diff --git a/AGENTS.md b/AGENTS.md index 6a92162e9..7444e9635 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -271,7 +271,11 @@ Since "Guides" is a Knock feature name, we should avoid using the word "guides" ## Images -Images should always use Image component, rounded-md, mx-auto, border, border-gray-200. +Images should always use the `Image` component from `lib/mdxComponents.tsx`. The component automatically applies `rounded-md`, `border`, and `border-gray-200` styling β€” do not add these classes manually in `className`. + +- Use `className="mx-auto"` to center images. +- Use `border={false}` to disable the default border (e.g. for logos or badges). +- When wrapping an image in a `
` tag, add `className="text-center"` to the `
` for `mx-auto` centering to work (the Image wrapper uses `inline-block`). ## List formatting with bold introductory text diff --git a/components/ui/Attributes.tsx b/components/ui/Attributes.tsx index 99c036509..d69c2a559 100644 --- a/components/ui/Attributes.tsx +++ b/components/ui/Attributes.tsx @@ -1,3 +1,5 @@ +import { useState } from "react"; +import { motion } from "framer-motion"; import { PropertyRow } from "./ApiReference/SchemaProperties/PropertyRow"; const Attributes = ({ children }) => { @@ -11,6 +13,9 @@ type Props = { typeSlug?: string; nameSlug?: string; isRequired?: boolean; + /** When provided, shows a "Show X" / "Hide X" toggle and renders children in a collapsible section. */ + expandLabel?: string; + children?: React.ReactNode; }; const Attribute = ({ @@ -19,18 +24,46 @@ const Attribute = ({ description, typeSlug, isRequired, + expandLabel, + children, }: Props) => { + const [isOpen, setIsOpen] = useState(false); + const hasNested = !!expandLabel && !!children; + return ( {name} - {/* Pass an optional `typeSlug` to link the `type` to its definition in the docs*/} + {/* Pass an optional `typeSlug` to link the `type` to its definition in the docs */} {type && {type}} {isRequired && } {description && ( {description} )} + {hasNested && ( + <> + setIsOpen(!isOpen)} + > + {isOpen ? `Hide ${expandLabel}` : `Show ${expandLabel}`} + + + + {children} + + + + )} ); }; diff --git a/components/ui/Autocomplete.tsx b/components/ui/Autocomplete.tsx index 36ea233c4..c9442aead 100644 --- a/components/ui/Autocomplete.tsx +++ b/components/ui/Autocomplete.tsx @@ -29,7 +29,7 @@ import { MenuItem } from "@telegraph/menu"; import { Tag } from "@telegraph/tag"; import { Code, Text } from "@telegraph/typography"; -import { DocsSearchItem, EndpointSearchItem } from "@/types"; +import { EnhancedDocsSearchItem, EndpointSearchItem } from "@/types"; import { useInkeepModal } from "../AiChatButton"; import { useAskAi } from "../AskAiContext"; @@ -62,7 +62,9 @@ function createAskAiPrompt(query: string): string { return `Can you tell me about ${query}`; } -type ResultItem = (DocsSearchItem & BaseItem) | (EndpointSearchItem & BaseItem); +type ResultItem = + | (EnhancedDocsSearchItem & BaseItem) + | (EndpointSearchItem & BaseItem); const algoliaAppId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || ""; const algoliaSearchApiKey = @@ -193,7 +195,7 @@ const DocsSearchResult = ({ )} - {item.section} + {item.pageTitle ? `${item.pageTitle as string} β€’` : ""} {item.section} ); @@ -400,7 +402,7 @@ const Autocomplete = () => { ], transformResponse({ hits: hitsArray }) { const hits = hitsArray as ( - | DocsSearchItem[] + | EnhancedDocsSearchItem[] | EndpointSearchItem[] )[]; // Add the "Ask AI" item at the top of the results @@ -823,7 +825,7 @@ const Autocomplete = () => { /> ) : ( autocomplete.setQuery("")} /> )} diff --git a/components/ui/Callout.tsx b/components/ui/Callout.tsx index 17b737269..fcfdf8fdf 100644 --- a/components/ui/Callout.tsx +++ b/components/ui/Callout.tsx @@ -9,7 +9,8 @@ type CalloutType = | "alert" | "enterprise" | "beta" - | "roadmap"; + | "roadmap" + | "community_sourced"; const TYPE_CONFIG: Record< CalloutType, @@ -24,6 +25,26 @@ const TYPE_CONFIG: Record< enterprise: { emoji: "🏒", bgColor: "blue" }, beta: { emoji: "🚧", bgColor: "yellow" }, roadmap: { emoji: "πŸ›£", bgColor: "default" }, + community_sourced: { emoji: "🀝", bgColor: "default" }, +}; + +const COMMUNITY_SOURCED = { + title: "Community-sourced.", + text: ( + <> + This tutorial is based on a real Knock implementation and may reflect + preferences of the original developer. If you spot something that could be + improved, we welcome{" "} + + contributions + + . Join our{" "} + + community Slack + {" "} + to discuss this implementation or share your own. + + ), }; export const Callout = ({ @@ -60,6 +81,17 @@ export const Callout = ({ ? TYPE_CONFIG[effectiveType]?.bgColor ?? customBgColor ?? "default" : customBgColor || "default"; + const effectiveTitle = + title ?? + (effectiveType === "community_sourced" + ? COMMUNITY_SOURCED.title + : undefined); + const effectiveText = + text ?? + (effectiveType === "community_sourced" + ? COMMUNITY_SOURCED.text + : undefined); + // Ensure emoji is always a string const emojiString = typeof emoji === "string" ? emoji : String(emoji || "πŸ’‘"); const centeredProps: TgphComponentProps = isCentered @@ -119,17 +151,17 @@ export const Callout = ({ py="1" style={{ marginBottom: "0px", color: "var(--tgph-gray-12)" }} > - {title && ( + {effectiveTitle && ( - {title}{" "} + {effectiveTitle}{" "} )} - {text && text} + {effectiveText && effectiveText} ); diff --git a/components/ui/CodeBlock.tsx b/components/ui/CodeBlock.tsx index b67df6532..01a24f1d4 100644 --- a/components/ui/CodeBlock.tsx +++ b/components/ui/CodeBlock.tsx @@ -14,6 +14,7 @@ import yaml from "react-syntax-highlighter/dist/cjs/languages/hljs/yaml"; import kotlin from "react-syntax-highlighter/dist/cjs/languages/hljs/kotlin"; import swift from "react-syntax-highlighter/dist/cjs/languages/hljs/swift"; import bash from "react-syntax-highlighter/dist/cjs/languages/hljs/bash"; +import xml from "react-syntax-highlighter/dist/cjs/languages/hljs/xml"; import { useClipboard } from "@/hooks/useClipboard"; import { useTheme } from "@/components/theme/ThemeProvider"; @@ -44,6 +45,9 @@ SyntaxHighlighter.registerLanguage("yaml", yaml); SyntaxHighlighter.registerLanguage("curl", bash); SyntaxHighlighter.registerLanguage("swift", swift); SyntaxHighlighter.registerLanguage("kotlin", kotlin); +SyntaxHighlighter.registerLanguage("xml", xml); +SyntaxHighlighter.registerLanguage("mjml", xml); +SyntaxHighlighter.registerLanguage("vue", xml); export type SupportedLanguage = | "javascript" @@ -186,7 +190,7 @@ export const CodeBlock: React.FC = ({ diff --git a/components/ui/CollapsibleNavItem.tsx b/components/ui/CollapsibleNavItem.tsx index ad127f583..698fc2f8e 100644 --- a/components/ui/CollapsibleNavItem.tsx +++ b/components/ui/CollapsibleNavItem.tsx @@ -15,6 +15,7 @@ export type CollapsibleNavItemProps = TgphComponentProps & { className?: string; color?: "default" | "gray"; isBeta?: boolean; + isLegacy?: boolean; }; export const CollapsibleNavItem = ({ @@ -25,6 +26,7 @@ export const CollapsibleNavItem = ({ color = "default", className = "", isBeta = false, + isLegacy = false, ...props }: CollapsibleNavItemProps) => { return ( @@ -52,6 +54,11 @@ export const CollapsibleNavItem = ({ Beta )} + {isLegacy && ( + + Legacy + + )} @@ -205,6 +205,11 @@ const ItemWithSubpages = ({ Beta )} + {page.isLegacy && ( + + Legacy + + )} ); @@ -248,6 +253,11 @@ const Item = ({ section, preSlug = "", depth = 0, defaultOpen }: ItemProps) => { Beta )} + {section.isLegacy && ( + + Legacy + + )} ); }; diff --git a/content/__api-reference/content.mdx b/content/__api-reference/content.mdx index bd5bfb3d3..5465c81a1 100644 --- a/content/__api-reference/content.mdx +++ b/content/__api-reference/content.mdx @@ -81,7 +81,7 @@ Each endpoint in the Knock API is rate limited. Knock uses a tier system to dete Knock's default behavior scopes rate limits based on the authorizing credential used in your requests. When you use a public or private API key to authorize a request, Knock will scope the rate limit for each endpoint by the [environment](/concepts/environments) associated with the key. If you use a signed user token as your authorizing credential, Knock will scope the rate limit by both the key's environment and the signing user. See our documentation on [enhanced security mode](/in-app-ui/security-and-authentication#authentication-with-enhanced-security) for more details on working with signed user tokens. -If you're concerned about exceeding a Knock rate limit, please [contact us](https://knock.app/contact-sales) and we can help figure out a usage rate that's right for your specific needs. +If you're concerned about exceeding a Knock rate limit, please contact us and we can help figure out a usage rate that's right for your specific needs. @@ -144,7 +144,7 @@ Knock can apply batch deduplication rate limits to all or part of a request. If
-Knock supports [idempotency](https://en.wikipedia.org/wiki/Idempotence) so that requests can be retried safely without unintended side effects. +Knock supports idempotency so that requests can be retried safely without unintended side effects. To perform an idempotent request, set an `Idempotency-Key` header on your request. This idempotency key is a unique string of up to 255 characters that you generate for each request. It is used to identify and prevent the duplicate processing of requests. If you retry a request with the same idempotency key within 24 hours from the original request, Knock will return the same response as the original request. Idempotent requests are expected to be identical. To prevent accidental misuse, Knock returns an error when incoming parameters don't match those from the original request. @@ -209,6 +209,36 @@ In some cases, a bulk endpoint will accept a large set of entities to perform so See the [Bulk operations section](/api-reference/bulk_operations) for more information on parsing and polling bulk operation statuses. +### Available bulk endpoints + +The following bulk endpoints are available in the Knock API: + +**Users** + +- [Bulk identify users](/api-reference/users/bulk/identify) +- [Bulk set preferences](/api-reference/users/bulk/set_preferences) +- [Bulk delete users](/api-reference/users/bulk/delete) + +**Objects** + +- [Bulk set objects](/api-reference/objects/bulk/set) +- [Bulk add subscriptions](/api-reference/objects/bulk/add_subscriptions) +- [Bulk delete objects](/api-reference/objects/bulk/delete) +- [Bulk delete subscriptions](/api-reference/objects/bulk/delete_subscriptions) + +**Tenants** + +- [Bulk set tenants](/api-reference/tenants/bulk/set) +- [Bulk delete tenants](/api-reference/tenants/bulk/delete) + +**Schedules** + +- [Create schedules in bulk](/api-reference/schedules/bulk/create) + +**Messages** + +- [Bulk update message statuses for channel](/api-reference/channels/bulk/update_message_status) +
@@ -337,7 +367,7 @@ Resources that return multiple entities support the same cursor-based pagination
-Knock uses standard [HTTP response codes](https://developer.mozilla.org/en-US/Web/HTTP/Status) to indicate the success or failure of your API requests. +Knock uses standard HTTP response codes to indicate the success or failure of your API requests. - `2xx` success status codes confirm that your request worked as expected. diff --git a/content/__mapi-reference/content.mdx b/content/__mapi-reference/content.mdx index ed39974c9..a59ec2bdb 100644 --- a/content/__mapi-reference/content.mdx +++ b/content/__mapi-reference/content.mdx @@ -72,7 +72,7 @@ Authorization: Bearer knock_st_12345
-Knock uses standard [HTTP response codes](https://developer.mozilla.org/en-US/Web/HTTP/Status) to indicate the success or failure of your API requests. +Knock uses standard HTTP response codes to indicate the success or failure of your API requests. - `2xx` success status codes confirm that your request worked as expected. - `4xx` error status codes indicate an error caused by incorrect or missing request information (e.g. providing an incorrect API key). @@ -84,7 +84,7 @@ Knock uses standard [HTTP response codes](https://developer.mozilla.org/en-US/We
-You can use our [Management API Postman collection](https://www.postman.com/knock-labs/workspace/knock-public-workspace/collection/15616728-9ed6000c-13bc-43f5-a2cf-db6daea256bd?action=share&creator=15616728&active-environment=15616728-6df39335-c6f9-4c9d-99d5-73e3c7ffe524) to quickly get started testing our Management API. +You can use our Management API Postman collection to quickly get started testing our Management API.
diff --git a/content/ai/agent-function.mdx b/content/ai/agent-function.mdx new file mode 100644 index 000000000..a6d298e1c --- /dev/null +++ b/content/ai/agent-function.mdx @@ -0,0 +1,40 @@ +--- +title: Agent function +description: Learn how the agent workflow function brings AI-powered enrichment and personalization into your Knock workflows. +tags: ["AI", "agent", "workflows", "functions", "personalization", "LLM"] +section: AI +--- + +A workflow with an agent step that qualifies sign-ups, showing the agent configuration and response + +The agent function is a workflow step that runs a prompt on an AI model of your choice and makes the response available in your workflow run. You can use it to enrich recipient data, personalize messaging, and bring AI-powered context into your notification flows. + +Common use cases include: + +- **Enriching recipient data.** Use user and tenant properties (such as domain) to understand a recipient's market, use cases, and target persona. +- **Personalizing messaging.** Bring that context into your [channel step templates](/template-editor/overview) to drive higher conversion rates. +- **Summarizing batch content.** Distill heterogeneous actions into a concise summary that reduces noise in digest notifications. + + + The agent function is a workflow step that runs inside a workflow run. It + is separate from the Knock agent, the + conversational assistant you use in the dashboard. + + } +/> + +## Learn more + +The agent function lives alongside Knock's other workflow functions. To learn how it works, how to configure a prompt and response format, and how credits and billing work, see the [full reference](/designing-workflows/ai-agent-function) in the designing workflows docs. + +For a tour of the other ways you can use AI across Knock, see the [Knock AI overview](/ai/overview). diff --git a/content/ai/agent.mdx b/content/ai/agent.mdx new file mode 100644 index 000000000..32dc1fe73 --- /dev/null +++ b/content/ai/agent.mdx @@ -0,0 +1,117 @@ +--- +title: Knock agent +description: Use the Knock agent in the dashboard to answer questions, inspect resources, and build customer messaging through conversation. +tags: ["AI", "agent", "dashboard", "assistant"] +section: AI +--- + +The Knock Agent dashboard showing conversation history and prompt interface + +The Knock agent is an AI-powered assistant built into the Knock dashboard. + +You can use it to do anything you'd normally do in the dashboard, including create workflows, templates, guides, broadcasts, and partials. You can also use the Knock agent to learn about Knock concepts and inspect existing resources. + + + The Knock agent is separate from the{" "} + AI agent function, a + workflow step that runs inside a workflow to enrich data. + + } +/> + +## Get started with the agent + +### Start from the Agents page + +The Knock agent is available on the **Agents** page in the dashboard. This page lists all of your past conversations with the agent, and allows you to create a new conversation. + +### Start from anywhere in the Knock dashboard + +On any dashboard page where the agent is enabled, you can use the **agent prompt bar** at the bottom of the page to kick off a new conversation. + +Once the agent is running, you can open the agent in a **sidebar** (anchored) or **floating** (overlay) layout to see it running. Use **Cmd+J** (Mac) or **Ctrl+J** (Windows/Linux), or the toggle in the main header to open/close the agent runner. + +## What the Knock agent can do + +### Read and inspect resources + +The agent has full access to all of your Knock resources, including workflows, broadcasts, partials, email layouts, audiences, message types, and guides. + +### Create and modify resources + +The agent can create or update workflows, broadcasts, partials, email layouts, audiences, message types, and guides using dedicated tools. Each maps to a customer messaging primitive you manage in the dashboard. + +### Bulk updates and refactors + +Because the agent operates across all of your resources in a single plan, you can use it to make changes that span multiple workflows at once. For example: + +- Update notification copy across every workflow template in a single instruction +- Update a partial's content or options and propagate the change across all templates that reference it +- Apply a formatting or structural change across all your email layouts + +This makes the agent useful not just for building new resources, but for maintaining consistency across your messaging as your product evolves. + +### Search the web + +The agent can run a **web search** for structured results and **fetch** full page content when you need up-to-date or external reference material. + +## What the agent cannot do + +- Manage account settings such as billing, users, roles, or invites +- Delete resources +- Commit or promote changes to environments +- Return usage or analytics data +- Run tests of your workflows or broadcasts + +## Context awareness + +The agent receives context for what you are viewing in the dashboard, including: + +- **Active environment.** Are you in development, production, or another environment. +- **Active resource keys.** The workflow, broadcast, partial, so on that you're currently viewing. +- **System context.** You can configure account-level context and custom instructions in your account AI settings that are used in all prompts. + +The agent is also fully aware of your partials, email layouts, and translations and so can use those to build or improve your messaging templates. + +## Permissions and authorization + +The agent respects the permissions you have in your account. If you don't have permission to manage a resource, the agent will not be able to create or update it either. + +## Configuration + +Workspace admins configure AI under **Settings** > **AI settings**: + +- **Agent workflow function.** Lets builders add an AI agent step to workflows; **consumes** AI credits. +- **Knock agents.** Enables the conversational assistant for account members; **does not** consume AI credits (rollout may use feature flags such as `dashboard-agent-mode`.) +- **Company context.** Free-text description of your product so answers stay on-brand (up to 5,000 characters.) +- **Custom instructions.** Free-text rules included in every agent conversation (up to 5,000 characters.) + +## Related resources + +- See how the agent fits alongside Knock's other AI primitives in the [Knock AI overview](/ai/overview) +- Connect external clients through the [MCP server](/ai/mcp-server) +- Set up coding agents with the [Knock CLI](/ai/cli) and [skills](/ai/skills) + +## Frequently asked questions + + + + No. The conversational Knock agent does not consume AI credits. The{" "} + AI agent function{" "} + inside workflows does. + + + Yes. See the MCP server docs for OAuth setup + and tool groups. + + diff --git a/content/ai/cli.mdx b/content/ai/cli.mdx new file mode 100644 index 000000000..2daf1a4f9 --- /dev/null +++ b/content/ai/cli.mdx @@ -0,0 +1,50 @@ +--- +title: Knock CLI +description: Use the Knock CLI as the local-first surface for AI coding agents to read and write Knock resources from your codebase. +section: AI +--- + +The [Knock CLI](/developer-tools/knock-cli) is the local-first AI agent surface for Knock. It's fast, lightweight, and leans on tooling already available in your code editor. Reach for the CLI when you're working in Cursor, Claude Code, or another IDE on a Knock-aware codebase and you want the agent to manage workflows, templates, and other resources alongside your application code. + +## What an agent can do with the CLI + +When the Knock CLI is installed and authenticated, an AI coding agent can use it to: + +- Pull workflows, templates, email layouts, partials, guides, and message types into your repo. +- Scaffold new resources locally, including new partials and guides. +- Edit resources as code, then validate them before pushing. +- Push changes back to Knock, commit them in an environment, and promote between environments. +- Wire Knock into your CI/CD pipeline. + +The CLI exposes the full set of Knock resource operations. The [MCP server](/ai/mcp-server) exposes a curated subset focused on managing resources, manipulating data, and inspecting environments β€” it does not currently support local file scaffolding, local validation, or deletion. Use the CLI when you need any of those. + +## Install the Knock CLI + +Install the CLI with `npm`: + +```bash title="Installing the Knock CLI" +npm install -g @knocklabs/cli +``` + +Then authenticate with `knock login`. See the [Knock CLI docs](/developer-tools/knock-cli) for full installation, authentication, and command reference. + +## Pair with the `knock-cli` skill + +The CLI gives an agent the tools to manage Knock resources. The [`knock-cli` skill](/ai/skills) gives the agent the procedural knowledge to use those tools well β€” which commands exist, when to reach for each one, and how to structure the resource files the CLI expects. + +Install the skill alongside the CLI: + +```bash title="Installing the knock-cli skill" +npx skills add knocklabs/skills --skill knock-cli +``` + +When you ask "add a comment-created workflow to this repo," the agent reads the skill, runs the right `knock` commands, and writes the resulting files into your project. + +You can pair this setup with the [MCP server](/ai/mcp-server) when you also want the agent to perform live API operations against your Knock account from outside the IDE. + +## See also + +- [Knock AI overview](/ai/overview). See how the CLI fits alongside the dashboard agent, agent function, MCP server, and skills. +- [Knock CLI reference](/developer-tools/knock-cli). Full installation, authentication, and command reference. +- [Skills](/ai/skills). Procedural knowledge that complements the CLI and MCP server. +- [MCP server](/ai/mcp-server). Connect external clients and your own apps to Knock as tools. diff --git a/content/ai/mcp-server.mdx b/content/ai/mcp-server.mdx new file mode 100644 index 000000000..3a7d2b8df --- /dev/null +++ b/content/ai/mcp-server.mdx @@ -0,0 +1,151 @@ +--- +title: Knock MCP server +description: Use the Knock MCP server to make Knock accessible to LLMs and AI agents via tool calling. +section: AI +--- + +Knock ships a remote MCP server at `mcp.knock.app/mcp` that exposes the primitives of Knock to LLMs and AI via the Model Context Protocol (MCP) so that your AI agents can discover and use Knock via tool calling. Reach for the MCP server when you need Knock tools available inside Claude Desktop, ChatGPT, or any MCP-compatible client, or when you're building a product that needs Knock primitives behind an LLM. + +Here are some examples of how you can use the MCP server in your workflow: + +- **Create workflows using natural language.** "Create a welcome email workflow for my B2B SaaS app." +- **Trigger a specific workflow to test your integration.** "Trigger the comment-created workflow for Dennis Nedry." +- **Create a set of test user and tenant data in your account.** "Create a user called Dennis Nedry and a tenant called acme-corp." + +## Get started + +The Knock MCP server is a remote serverβ€”no local installation or Node.js setup is required. You connect to `https://mcp.knock.app/mcp` directly from your MCP client and authenticate using your Knock account via OAuth. This means your Knock credentials are managed securely through the standard sign-in flow rather than requiring a service token. + +We've added setup instructions below for Cursor, Claude Desktop, and Claude Code, but the same instructions apply to any other MCP client-compatible application. + +### Cursor + +1. Go to **Settings** > **Cursor Settings** and find the "Tools & Integrations" section. +2. Click "New MCP server" under **MCP Tools**. +3. Inside your `mcp.json` file under the `mcpServers` key, add the following: + +```json +{ + "knock": { + "url": "https://mcp.knock.app/mcp", + "name": "Knock MCP Server" + } +} +``` + +4. When Cursor prompts you to authenticate, sign in with your Knock account. + +### Claude Desktop + +Note: This setup uses `mcp-remote` as a local proxy, which requires Node.js 18 or higher. + +1. Open the Claude Desktop settings and find the "Developer" section. +2. Click "Edit Config." +3. Open your `claude_desktop_config.json` file in your preferred text editor. +4. Add the following contents to the file (or add to the `mcpServers` section if it exists): + +```json +{ + "mcpServers": { + "knock": { + "command": "npx", + "args": ["mcp-remote", "https://mcp.knock.app/mcp"] + } + } +} +``` + +5. Restart Claude Desktop. When you first use a Knock tool, you'll be prompted to sign in with your Knock account. + +### Claude Code + +1. Run the following command to add the Knock MCP server: + +```bash +claude mcp add --transport http knock https://mcp.knock.app/mcp +``` + +2. Start Claude Code and run `/mcp` to authenticate with your Knock account. + +## Tool groups + +When connecting to the Knock MCP server, you can choose exactly which groups of tools to enable. The MCP server exposes a curated subset of Knock capabilities β€” focused on managing resources and data, inspecting environments, and committing or promoting changes. Limiting the active tool groups to only what you need keeps the tool list manageable and reduces the risk of unintended changes. + +| Group | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------- | +| **Manage resources** | Create and manage notification workflows, channels, templates, email layouts, partials, and other configuration | +| **Commits** | Commit and promote changes across environments | +| **Debug** | Inspect environments and view sent message logs | +| **Manage data** | Manage users, tenants, and object data | +| **Documentation** | Search Knock documentation | + +If you need capabilities the MCP server doesn't expose β€” such as local file scaffolding for new resources, validating them before push, or working entirely offline against a checked-in repo β€” reach for the [Knock CLI](/ai/cli) instead. + +## What tools are available? + +The MCP server ships with tools to interact with all Knock resources. You can find the full list of available tools in the [tools reference](/developer-tools/agent-toolkit/tools-reference) of the Knock Agent Toolkit, which the MCP server is built on top of. + +Please note that at this time, the MCP server **does not** ship with any tools to delete resources. This is intentional to prevent the accidental deletion of resources in your Knock account. + +### Workflow-specific tools + +The Knock MCP server exposes a full suite of tools for creating and managing workflows. Using the MCP server you can: + +- Create a workflow with natural language: "create a workflow that sends a welcome email to new users" +- Create a delay or batch step within your workflow: "delay for 3 days" or "batch for 10 minutes" +- Create an email step within your workflow: "create a credit card expiring email with a link back to the dashboard" +- Create an SMS, push, or in-app feed step within your workflow + +Using these tools you can create a complex prompt that describes one or more workflows that you'd like to create with natural language. + +## Workflows-as-tools + +The Knock MCP server also supports exposing your workflows as individual tools. This gives the LLM a specific and precise interface for invoking workflow triggers, including describing the data trigger requirements for your workflows. + +By default, the MCP server will **not** expose any workflows as tools. To opt into this behavior, contact us or refer to your MCP client's tool configuration options. + +## Pair with skills + +The MCP server gives an agent the tools to operate on Knock. [Skills](/ai/skills) give the agent the procedural knowledge to operate on Knock _well_. The two are complementary, and most teams using MCP install at least one skill alongside it. + +Knock's [`notification-best-practices`](/ai/skills) skill pairs naturally with the MCP server. It teaches an agent how to write effective notification copy across email, SMS, push, and in-app channels β€” guidance that applies regardless of whether the agent reaches Knock through MCP, the CLI, or the dashboard agent. + +Install it with: + +```bash +npx skills add knocklabs/skills --skill notification-best-practices +``` + +See the [skills page](/ai/skills) for the full catalog of available skills and which surface each one complements. + +## Related links + +- [Knock AI overview](/ai/overview) +- [Knock agent](/ai/agent) +- [CLI](/ai/cli) +- [Skills](/ai/skills) +- [Building with LLMs](/developer-tools/building-with-llms) +- [Knock Agent Toolkit](/developer-tools/agent-toolkit/overview) + +## Frequently asked questions + + + + Some MCP clients will warn you about having more than 50 tools. To address + this, enable only the tool groups you need for the task at hand. For + example, if you're only managing user data, enable the **Manage data** group + and leave the others disabled. + + + If you see an error like _"The model returned an error. Try disabling MCP + servers, or switch models,"_ check which model is selected for the Cursor + agent. Make sure it's explicitly set to a supported model like + `claude-sonnet-4` rather than relying on automatic model selection. + + + The remote MCP server at `mcp.knock.app/mcp` uses OAuth-based authentication + and requires signing in with your Knock account. If you need service + token-based authentication for a self-hosted or CI environment, please + contact us. + + diff --git a/content/ai/overview.mdx b/content/ai/overview.mdx new file mode 100644 index 000000000..ff72a0cbc --- /dev/null +++ b/content/ai/overview.mdx @@ -0,0 +1,139 @@ +--- +title: Knock AI overview +description: Use AI features across Knock β€” in the dashboard, inside workflows, in your IDE, and in external clients. +section: AI +--- + +Knock exposes AI functionality across several distinct primitives: a conversational assistant in the dashboard, an AI step inside workflows, a local CLI for IDE-based agents, a remote MCP server for external clients and your own apps, and a set of open-source skills that give agents procedural knowledge for working with Knock. Use this page to understand each one and which to reach for. + + + + + + + + + +## Knock agent + +The [Knock agent](/ai/agent) is a conversational assistant built into the Knock dashboard. It can do anything you'd normally do in the dashboard, with full context for the resource you're viewing and your active environment. + +The Knock agent in the dashboard + +Use the Knock agent to: + +- Build workflows, broadcasts, partials, email layouts, audiences, message types, and guides through conversation. +- Inspect existing resources and ask questions about how your messaging is configured. +- Pull in account-level company context and custom instructions so responses stay on-brand. +- Run bulk updates and refactors across resources β€” for example, update copy across multiple workflow templates, update a partial's content or options, or apply a formatting change across all your email layouts in a single plan. + +The Knock agent does not consume AI credits and is configured under **Settings** > **AI settings**. + +## Agent function + +The [agent function](/ai/agent-function) is a workflow step that runs a prompt on an AI model of your choice and makes the response available in the rest of your workflow run. Use it to bring AI-powered context into the notifications you send. + +A workflow with an agent step + +Common use cases include: + +- **Enrich recipient data.** Use user and tenant properties to infer market, persona, or use case. +- **Summarize batched activity.** Distill a batch of heterogeneous events into a concise digest summary. +- **Classify or route triggers.** Tag a sign-up, support ticket, or comment so downstream steps can branch on it. + +The agent function consumes AI credits and is configured per workflow step. See the [full reference](/designing-workflows/ai-agent-function) for prompt and response format details. + +## CLI + +**Recommended when you're working in an IDE on a Knock-aware codebase.** The [Knock CLI](/ai/cli) gives a coding agent the local tools it needs to read and write Knock resources from your codebase. Reach for the CLI when you're editing code in Cursor, Claude Code, or Copilot and want the agent to manage workflows, templates, and other resources alongside your application code. + +The CLI is the local-first surface and supports the full set of Knock resource operations, including local file scaffolding for new resources, local validation, and committing or promoting changes between environments. + +## MCP server + +**Recommended for external clients and your own apps.** The [MCP server](/ai/mcp-server) at `mcp.knock.app/mcp` exposes Knock primitives to LLMs and AI agents via the Model Context Protocol. Reach for the MCP server when you need Knock tools available inside Claude Desktop, ChatGPT, or any MCP-compatible client, or when you're building a product that needs Knock primitives behind an LLM. + +The MCP server exposes a curated subset of Knock capabilities organized into tool groups (manage resources, commits, debug, manage data, documentation). It does not currently support local file scaffolding, local validation, or deletion β€” use the CLI for those. + + + The MCP server is built on top of the{" "} + Knock Agent Toolkit, + a lower-level SDK that lets you choose which tools to expose, configure + the auth model, and bring your own framework. + + } +/> + +## Skills + +**Recommended alongside the CLI, the MCP server, or the dashboard agent.** [Skills](/ai/skills) are packaged procedural knowledge that an AI agent loads automatically when relevant. They sit alongside your tool surfaces and teach an agent _how_ to use those tools effectively. + +Skills come from two places: + +- **Knock's open-source package** at github.com/knocklabs/skills, installed into AI coding agents like Cursor or Claude Code. Includes `knock-cli` (pairs with the [CLI](/ai/cli)) and `notification-best-practices` (pairs with any surface). +- **Skills authored inside the [Knock agent](/ai/agent)** in the dashboard, which the dashboard agent invokes automatically during agent runs. Useful for codifying team-specific conventions and account-level context. + +## Choosing between the CLI and MCP server + +The CLI and MCP server are different tool for different environments. You can use this table to figure out which one fits your task. + +| Question | Reach for | +| --------------------------------------------------------------- | ---------------------------- | +| Are you working in an IDE on a Knock-aware codebase? | [CLI](/ai/cli) | +| Do you need to scaffold, validate, or commit resources locally? | [CLI](/ai/cli) | +| Are you using an external GUI like Claude Desktop? | [MCP server](/ai/mcp-server) | +| Are you embedding Knock tools in your own product? | [MCP server](/ai/mcp-server) | +| Do you need ad-hoc natural-language operations? | [MCP server](/ai/mcp-server) | + + + +## Related + +- [Knock Agent Toolkit](/developer-tools/agent-toolkit/overview). The SDK behind the MCP server, for fully custom AI integrations. +- [Building with LLMs](/developer-tools/building-with-llms). Patterns and examples for putting Knock behind an LLM. +- **Settings > AI settings** in the dashboard. Configure company context and custom instructions used across Knock AI. diff --git a/content/ai/skills.mdx b/content/ai/skills.mdx new file mode 100644 index 000000000..9682ce76f --- /dev/null +++ b/content/ai/skills.mdx @@ -0,0 +1,94 @@ +--- +title: Skills +description: Use skills to give AI agents the procedural knowledge they need to work with Knock effectively, whether they run in your IDE, an external client, or the Knock dashboard. +section: AI +--- + +Skills are packaged instructions and rules that extend an AI agent's capabilities with Knock-specific procedural knowledge. They sit alongside your tool surfaces β€” the [Knock CLI](/ai/cli), the [MCP server](/ai/mcp-server), and the [Knock agent](/ai/agent) in the dashboard β€” and teach the agent _how_ to use those tools to accomplish real work. + +Once a skill is available to an agent, it auto-activates when you ask the agent to work on a related task. There's no manual prompting required. + +Skills come from two places at Knock: + +- **The open-source `knocklabs/skills` package** that you install into an AI coding agent (Cursor, Claude Code, and similar). These are the skills covered in [Available skills](#available-skills) below. +- **Your own skills authored inside the [Knock agent](/ai/agent)** in the dashboard. The dashboard agent supports creating skills directly in your account and invokes them automatically during agent runs, which is useful for codifying team-specific conventions that aren't captured in the open-source package. + + + +## Install Knock's open-source skills in a coding agent + +Knock publishes an open-source skills package on GitHub at github.com/knocklabs/skills. Install it into any compatible AI coding agent (Cursor, Claude Code, and similar) so the agent can use Knock's skills when working on your codebase. + +Install the entire package with a single command: + +```bash title="Installing the Knock skills package" +npx skills add knocklabs/skills +``` + +Or install specific skills by passing a flag: + +```bash title="Installing individual Knock skills" +npx skills add knocklabs/skills --skill knock-cli +npx skills add knocklabs/skills --skill notification-best-practices +``` + +## Author skills in the Knock dashboard agent + +The [Knock agent](/ai/agent) in the dashboard also supports skills. You can create skills directly in your Knock account and the dashboard agent invokes them automatically during agent runs. + +Use dashboard skills to: + +- Codify team-specific conventions that aren't captured in the open-source package (for example, your internal naming standards, copy guidelines, or workflow patterns). +- Standardize how the agent handles common requests across your account. +- Layer organizational context on top of Knock's built-in agent behavior. + +The open-source package and dashboard-authored skills are complementary. A team might install `notification-best-practices` from the open-source package and also author an internal "transactional email tone of voice" skill in the dashboard. + +## Available skills + +### `knock-cli` + +The `knock-cli` skill teaches AI agents how to use the [Knock CLI](/developer-tools/knock-cli) to manage your Knock resources from your codebase. + +**Complements.** The [Knock CLI](/ai/cli). + +Use it when you want an agent to: + +- Pull workflows, templates, guides, and partials to your local machine. +- Push local changes back to Knock. +- Manage Knock resources as part of a development or CI/CD workflow. + +### `notification-best-practices` + +The `notification-best-practices` skill gives AI agents comprehensive guidelines for notification design and implementation. + +**Complements.** Any surface β€” the [Knock CLI](/ai/cli), the [MCP server](/ai/mcp-server), and the [Knock agent](/ai/agent) in the dashboard. Because this skill is about content and UX rather than how the agent reaches Knock, it applies regardless of which surface the agent uses. + +Use it when you want an agent to: + +- Write notification copy across channels (email, SMS, push, in-app). +- Apply best practices for transactional and welcome email templates. +- Follow channel-specific formatting and content guidelines. + +## How skills fit with the CLI, MCP server, and dashboard agent + +Skills are not tied to a single tool surface. Different skills complement different surfaces, and many teams install or author more than one: + +| Skill | Pairs with | What it adds | +| ----------------------------- | --------------------------------------------- | ------------------------------------------------------------------ | +| `knock-cli` | The [Knock CLI](/ai/cli) | How to scaffold, edit, and push resource files from your codebase. | +| `notification-best-practices` | Any surface (CLI, MCP, dashboard agent) | How to write effective notifications across channels. | +| Dashboard-authored skills | The [Knock agent](/ai/agent) in the dashboard | Team-specific conventions and account-level context. | + +When an agent has both a tool surface and the right skill available, it can do more with fewer prompts: the tools give it reach into Knock, and the skill gives it the patterns to use those tools well. + +## See also + +- [Knock AI overview](/ai/overview). See how skills fit alongside the dashboard agent, agent function, CLI, and MCP server. +- [Knock agent](/ai/agent). The in-dashboard assistant where you can author and invoke skills directly. +- [Knock CLI](/ai/cli). The local-first agent surface that pairs with the `knock-cli` skill. +- [MCP server](/ai/mcp-server). Connect external clients and your own apps to Knock as tools. diff --git a/content/cli/audience.mdx b/content/cli/audience.mdx new file mode 100644 index 000000000..67a804045 --- /dev/null +++ b/content/cli/audience.mdx @@ -0,0 +1,342 @@ +--- +title: Audiences +description: Commands for managing audiences in the Knock CLI. +--- + +
+ + +Audience commands enable you to manage audiences in your Knock account from the CLI. + + +
+ +
+ + +When audiences are pulled from Knock, they are stored in directories named by their audience key. Each audience directory contains an `audience.json` file that describes the audience's configuration (name, type, and for dynamic audiences, segments). + +{/* prettier-ignore */} + +{`audiences/ +└── new-signups/ + └── audience.json`} + + +If you're migrating your local audience files into Knock, you can arrange them using the example file structure above and then push them into Knock with a single command using [`knock audience push --all`](/cli/audience/push). + + +
+ +
+ + +Display all audiences for an environment. Use an `--environment` flag to specify the target environment; if omitted, the Knock CLI defaults to the development environment. + +### Flags + + + + + + + + + + + + + + +```bash title="Basic usage" +knock audience list +``` + +```bash title="Pagination example" +knock audience list --after=xxx +``` + + +
+ +
+ + +Display a single audience from an environment. + +Use an `--environment` flag to specify the target environment; if omitted, the Knock CLI defaults to the development environment. + +### Flags + + + + + + + + + + + +```bash title="Basic usage" +knock audience get new-signups +``` + +```bash title="Get audience in a different environment" +knock audience get new-signups --environment=production +``` + + +
+ +
+ + +Pull one or more audiences from an environment into a local file system. Knock CLI will create a new audience directory or update the existing audience directory in the local file system. + +By default this command will resolve to the audiences resource directory via your `knock.json` file. When not set, will use the current working directory as the default. In the case of the `--all` flag, the target directory path will be resolved via your `knock.json` file or the `--audiences-dir` flag. + +Note: if pulling the target audience for the first time (or all audiences), Knock CLI will ask to confirm before writing to the local file system. + +See the [Audience file structure](/cli/audience/file-structure) section for details on how audience files are organized. + +### Flags + + + + + + + + + + + + + + +```bash title="Basic usage" +knock audience pull new-signups +``` + +```bash title="Pulling an audience in a different environment" +knock audience pull new-signups --environment=production +``` + +```bash title="Pulling all audiences into ./audiences directory" +knock audience pull --all --audiences-dir=./audiences +``` + + +
+ +
+ + +Push one or more audiences from a local file system to Knock. Knock will update an existing audience by the matching audience key, or create a new audience if it does not exist yet. + +By default this command will resolve to the audiences resource directory via your `knock.json` file. When not set, will use the current working directory as the default. In the case of the `--all` flag, the target directory path will be resolved via your `knock.json` file or the `--audiences-dir` flag. + +Note: + +- You must be directly above the target audience directory when running the `audience push` command, so the CLI can locate the `audience.json` file. +- You can also pass in the `--commit` flag (with an optional `--commit-message` flag) to commit the upserted changes immediately. +- Use `--force` to bypass environment restrictions and overwrite content in any environment. If you're using this on a non-development environment, you should also ensure you commit the changes. + +See the [Audience file structure](/cli/audience/file-structure) section for details on how audience files are organized. + +### Flags + + + + + + + + + + + + + + +```bash title="Basic usage" +knock audience push new-signups +``` + +```bash title="Pushing an audience and committing with a message" +knock audience push new-signups \ + --commit \ + -m "Commit message" +``` + +```bash title="Pushing all audiences from ./audiences directory" +knock audience push --all --audiences-dir=./audiences +``` + + +
+ +
+ + +Validate one or more audiences from a local file system. Knock will validate the given audience payload in the same way as it would with the `audience push` command, except without persisting those changes. + +Note: Validating an audience is only done against the `development` environment. + +### Flags + + + + + + + + + +```bash title="Basic usage" +knock audience validate new-signups +``` + + +
+ +
+ + +Archive an audience across all environments. Archiving removes the audience from use but preserves its configuration. + + + +### Flags + + + + + + + + + + +```bash title="Basic usage" +knock audience archive old-audience --environment=development +``` + + +
diff --git a/content/cli/email-layout.mdx b/content/cli/email-layout.mdx index a8f75c5ce..64746f387 100644 --- a/content/cli/email-layout.mdx +++ b/content/cli/email-layout.mdx @@ -14,7 +14,7 @@ Email layout commands enable you to manage email layouts in your Knock account f
-When email layouts are pulled from Knock, they are stored in directories named by their layout key. +When email layouts are pulled from Knock, they are stored in directories named by their layout key. MJML layouts use the same file structure; the `html_layout.html` file contains MJML markup instead of HTML. See the [MJML support](/integrations/email/mjml) docs for details. {/* prettier-ignore */} @@ -265,6 +265,11 @@ See the [Layout file structure](/cli/email-layout/file-structure) section for de type="directory" description="Specifies the target directory path to find and push all email layouts from. Only available to be used with --all, and defaults to the current working directory." /> + diff --git a/content/cli/guide.mdx b/content/cli/guide.mdx index 66f169472..4cf55cefb 100644 --- a/content/cli/guide.mdx +++ b/content/cli/guide.mdx @@ -253,7 +253,7 @@ By default this command will resolve to the guides resource directory via your ` Note: -- The `guide push` command only pushes guides into the `development` environment. +- The `guide push` command only pushes guides into the `development` environment by default. Use `--force` to bypass this restriction and overwrite content in any environment. - You must be directly above the target guide directory when running the `guide push` command, so the CLI can locate the `guide.json` file. - You can also pass in the `--commit` flag (with an optional `--commit-message` flag) to commit the upserted changes immediately. @@ -282,6 +282,11 @@ See the [Guide file structure](/cli/guide/file-structure) section for details on type="directory" description="Specifies the target directory path to find and push all guides from. Only available to be used with --all, and defaults to the current working directory." /> + diff --git a/content/cli/message-type.mdx b/content/cli/message-type.mdx index ff003332e..5b74468a3 100644 --- a/content/cli/message-type.mdx +++ b/content/cli/message-type.mdx @@ -247,7 +247,7 @@ By default this command will resolve to the message types resource directory via Note: -- The `message-type push` command only pushes message types into the `development` environment. +- The `message-type push` command only pushes message types into the `development` environment by default. Use `--force` to bypass this restriction and overwrite content in any environment. - You must be directly above the target message type directory when running the `message-type push` command, so the CLI can locate the `message_type.json` file. - You can also pass in the `--commit` flag (with an optional `--commit-message` flag) to commit the upserted changes immediately. @@ -276,6 +276,11 @@ See the [Message type file structure](/cli/message-type/file-structure) section type="directory" description="Specifies the target directory path to find and push all message types from. Only available to be used with --all, and defaults to the current working directory." /> + diff --git a/content/cli/overview.mdx b/content/cli/overview.mdx index ede884d13..f8f612534 100644 --- a/content/cli/overview.mdx +++ b/content/cli/overview.mdx @@ -25,7 +25,7 @@ With the CLI, you can: **Install with Homebrew** -For macOS, you can install the Knock CLI using [Homebrew](https://brew.sh/). Once the CLI is installed you can call it by using the `knock` command in your terminal. +For macOS, you can install the Knock CLI using Homebrew. Once the CLI is installed you can call it by using the `knock` command in your terminal. **Install with npm** @@ -38,7 +38,7 @@ The Knock CLI is built with Node.js and installable as a `npm` package. You must - Node.js: 16.14.0 or higher - NPM: 7.18.1 or higher -You can find the Knock CLI npm package [here](https://www.npmjs.com/package/@knocklabs/cli). +You can find the Knock CLI npm package here. diff --git a/content/cli/partial.mdx b/content/cli/partial.mdx index b319fd75f..afa48d756 100644 --- a/content/cli/partial.mdx +++ b/content/cli/partial.mdx @@ -251,7 +251,7 @@ By default this command will resolve to the partial resource directory via your Note: -- The `partial push` command only pushes partials into the `development` environment. +- The `partial push` command only pushes partials into the `development` environment by default. Use `--force` to bypass this restriction and overwrite content in any environment. - You must be directly above the target partial directory when running the `partial push` command, so the CLI can locate the `partial.json` file. - You can also pass in the `--commit` flag (with an optional `--commit-message` flag) to commit the upserted changes immediately. @@ -278,6 +278,11 @@ Note: type="directory" description="Specifies the target directory path to find and push all partials from. Only available to be used with --all, and defaults to the current working directory." /> + diff --git a/content/cli/resources.mdx b/content/cli/resources.mdx index b6e5e0cc5..e79f15ff4 100644 --- a/content/cli/resources.mdx +++ b/content/cli/resources.mdx @@ -6,7 +6,7 @@ description: Commands for managing all Knock resources at once.
-These commands enable you to manage all Knock resources (workflows, partials, email layouts, translations, guides, and message-types) at once. +These commands enable you to manage all Knock resources (workflows, partials, email layouts, translations, guides, message-types, and audiences) at once.
@@ -39,7 +39,7 @@ knock init
-Pulls the contents of all Knock resources (workflows, partials, email layouts, translations, guides, and message-types) from Knock into your local file system. +Pulls the contents of all Knock resources (workflows, partials, email layouts, translations, guides, message-types, and audiences) from Knock into your local file system. Resources will be grouped by resource type within subdirectories of the target directory path set either by your `knock.json` file or by the `--knock-dir` flag. See the [Directory structure](/cli/overview/directory-structure) section for details on the directory structure used by `push` and `pull` commands. @@ -86,7 +86,7 @@ knock pull --knock-dir=./knock
-Pushes all local resource files (workflows, partials, email layouts, and translations) back to Knock and upserts them. +Pushes all local resource files (workflows, partials, email layouts, translations, guides, message-types, and audiences) back to Knock and upserts them. Resources will be pushed to the target directory path set either by your `knock.json` file or by the `--knock-dir` flag. See the [Directory structure](/cli/overview/directory-structure) section for details on the directory structure used by `push` and `pull` commands. @@ -108,6 +108,11 @@ Resources will be pushed to the target directory path set either by your `knock. type="string" description="The commit message to pass when using the `--commit` flag." /> + diff --git a/content/cli/translation.mdx b/content/cli/translation.mdx index a3a975aa2..325732d2b 100644 --- a/content/cli/translation.mdx +++ b/content/cli/translation.mdx @@ -231,6 +231,11 @@ See the [Translation file structure](/cli/translation/file-structure) section fo type="directory" description="Specifies the target directory path to find and push all translations from. Only available to be used with --all, and defaults to the current working directory." /> + diff --git a/content/cli/workflow.mdx b/content/cli/workflow.mdx index 853a9c92b..8023c6f42 100644 --- a/content/cli/workflow.mdx +++ b/content/cli/workflow.mdx @@ -272,7 +272,7 @@ By default this command will resolve to the workflows resource directory via you Note: -- The `workflow push` command only pushes workflows into the `development` environment. +- The `workflow push` command only pushes workflows into the `development` environment by default. Use `--force` to bypass this restriction and overwrite content in any environment. - You must be directly above the target workflow directory when running the `workflow push` command, so the CLI can locate the `workflow.json` file. - You can also pass in the `--commit` flag (with an optional `--commit-message` flag) to commit the upserted changes immediately. @@ -301,6 +301,11 @@ See the [Workflow file structure](/cli/workflow/file-structure) section for deta type="directory" description="Specifies the target directory path to find and push all workflows from. Only available to be used with --all, and defaults to the current working directory." /> + diff --git a/content/concepts/channels.mdx b/content/concepts/channels.mdx index 884e71fe5..5c9d57f29 100644 --- a/content/concepts/channels.mdx +++ b/content/concepts/channels.mdx @@ -10,7 +10,8 @@ A channel in Knock represents a configured provider to send notifications to you Within Knock, we split channels into different types, where each type has at least one provider associated that can be configured: - [Email](/integrations/email/overview) (such as Sendgrid, Postmark) -- [In-app](/integrations/in-app/overview) (such as feeds, toasts, banners) +- [In-app](/integrations/in-app/overview) (such as feeds and toasts) +- [In-app guide](/in-app-ui/guides/overview) (such as banners, modals, and other in-product messaging) - [Push](/integrations/push/overview) (such as APNs, FCM) - [SMS](/integrations/sms/overview) (such as Twilio, Telnyx) - [Chat](/integrations/chat/overview) (such as Slack, Microsoft Teams, and Discord) diff --git a/content/concepts/conditions.mdx b/content/concepts/conditions.mdx index 3ce369517..f433304e7 100644 --- a/content/concepts/conditions.mdx +++ b/content/concepts/conditions.mdx @@ -137,25 +137,25 @@ The timestamp operators (`is_timestamp_before`, `is_timestamp_on_or_after`, and You can use any of the following operators in condition comparisons: -| Operator | Description | -| -------------------------- | ---------------------------------------------------------------------------------------- | -| `equal_to` | `==` | -| `not_equal_to` | `!=` | -| `greater_than` | `>` | -| `greater_than_or_equal_to` | `>=` | -| `less_than` | `<` | -| `less_than_or_equal_to` | `<=` | -| `contains` | Checks whether `argument` is in `variable` (works with strings and lists) | -| `contains_all` | Checks whether all `argument` values are present in `variable` (for comparing lists) | -| `not_contains` | Checks whether `argument` is not in `variable` (works with strings and lists) | -| `not_contains_all` | Checks whether all `argument` values are not present in `variable` (for comparing lists) | -| `empty` | Checks whether `variable` is in `["", null, []]` | -| `not_empty` | Checks whether `variable` is not in `["", null, []]` | -| `exists` | Checks whether `variable` is provided and has a value that is not `null` | -| `not_exists` | Checks whether `variable` is not provided or has a `null` value | -| `is_timestamp_before` | Checks whether `variable` is a timestamp before the `argument` value | -| `is_timestamp_on_or_after` | Checks whether `variable` is a timestamp on or after the `argument` value | -| `is_timestamp_between` | Checks whether `variable` is a timestamp between the `argument`'s start and end values | +| Operator | Description | +| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `equal_to` | `==` | +| `not_equal_to` | `!=` | +| `greater_than` | `>` | +| `greater_than_or_equal_to` | `>=` | +| `less_than` | `<` | +| `less_than_or_equal_to` | `<=` | +| `contains` | Checks whether `argument` is in `variable` (works with strings and lists) | +| `contains_all` | Checks whether all `argument` values are present in `variable` (for comparing lists) | +| `not_contains` | Checks whether `argument` is not in `variable` (works with strings and lists) | +| `not_contains_all` | Checks whether all `argument` values are not present in `variable` (for comparing lists) | +| `empty` | Checks whether `variable` is in `["", null, []]`. If the variable is not provided at the path, Knock treats it as empty, so this returns true. | +| `not_empty` | Checks whether `variable` is not in `["", null, []]`. If the variable is not provided at the path, Knock treats it as empty, so this returns false. | +| `exists` | Checks whether `variable` is provided and has a value that is not `null` | +| `not_exists` | Checks whether `variable` is not provided or has a `null` value | +| `is_timestamp_before` | Checks whether `variable` is a timestamp before the `argument` value | +| `is_timestamp_on_or_after` | Checks whether `variable` is a timestamp on or after the `argument` value | +| `is_timestamp_between` | Checks whether `variable` is a timestamp between the `argument`'s start and end values | +
+
valid tz database time zone string, like `America/New_York` or `Europe/London`. By default, if no recipient timezone is set `Etc/UTC` will be used however a [default timezone](/manage-your-account/account-timezone) can be specified at the account level under "Settings" which will override this default for all recipients. ## Frequently asked questions diff --git a/content/concepts/schedules.mdx b/content/concepts/schedules.mdx index f85568ff5..cb2cc3cf8 100644 --- a/content/concepts/schedules.mdx +++ b/content/concepts/schedules.mdx @@ -269,7 +269,7 @@ There are 2 ways in which to get data into each of your scheduled workflow runs: ## Executing schedules in a recipient's timezone -Knock supports a `timezone` property on the recipient that automatically makes a scheduled workflow run timezone aware, meaning you can express recurring schedules like "every monday at 9am in the recipient's timezone." Recipient timezones must be a [valid tz database time zone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), like `America/New_York`. +Knock supports a `timezone` property on the recipient that automatically makes a scheduled workflow run timezone aware, meaning you can express recurring schedules like "every monday at 9am in the recipient's timezone." Recipient timezones must be a valid tz database time zone string, like `America/New_York`. [Read more about recipient timezone support](/concepts/recipients#recipient-timezones). diff --git a/content/concepts/subscriptions.mdx b/content/concepts/subscriptions.mdx index 1efd57ef7..b0ab9c5a3 100644 --- a/content/concepts/subscriptions.mdx +++ b/content/concepts/subscriptions.mdx @@ -90,6 +90,13 @@ await knock.workflows.trigger("alert-workflow", { }); ``` +#### Deduplication by default + +Knock always deduplicates recipients when executing a notification fan out, including for workflow triggers with subscriptions. Knock will ensure your notification workflow is executed only once for each unique recipient in the following cases: + +- When the recipient appears both in the initial trigger and as a subscriber to one of your objects. +- When the recipient appears multiple times within a nested subscription hierarchy. + ### Retrieving subscriptions for an object You can retrieve a paginated list of subscriptions for an object, which will return the `recipient` subscribed as well. @@ -132,6 +139,19 @@ As an example, if you have a property `role` under your subscription properties, } /> +## Referencing subscriptions in templates + +You can use the `subscriptions` Liquid filter to dynamically load up to 25 active subscriptions for a user in your notification templates. This is useful when you want to render content based on what a user is subscribed to without passing subscription data in the workflow trigger. + +```liquid title="Rendering a user's subscriptions in a template" +{% assign subs = recipient.id | subscriptions %} +{% for sub in subs %} + {{ sub.object.id }} - {{ sub.properties.role }} +{% endfor %} +``` + +For more details, see [referencing data in templates](/template-editor/referencing-data#referencing-subscriptions-via-the-subscriptions-filter). + ## Modeling nested subscription hierarchies It's possible to model nested subscription hierarchies by associating child objects as subscribers of a parent object. This allows you to create structures like "organizations" having many "teams" which have many "team members" (users). @@ -160,13 +180,6 @@ Once you've established a nested hierarchy like the above, it's also possible to } /> -## Deduplication by default - -Knock always deduplicates recipients when executing a notification fan out, including for workflow triggers with subscriptions. Knock will ensure your notification workflow is executed only once for each unique recipient in the following cases: - -- When the recipient appears both in the initial trigger and as a subscriber to one of your objects. -- When the recipient appears multiple times within a nested subscription hierarchy. - ## Frequently asked questions diff --git a/content/concepts/tenants.mdx b/content/concepts/tenants.mdx deleted file mode 100644 index 84f5e800a..000000000 --- a/content/concepts/tenants.mdx +++ /dev/null @@ -1,280 +0,0 @@ ---- -title: Tenants -description: Learn how to use tenants to map your multi-tenant structure to Knock and power per-user, per-tenant notification experiences. -tags: - ["tenant", "tenancy", "saas", "how knock works", "custom brand", "branding"] -section: Concepts ---- - -Tenants represent segments your users belong to. You might call these "accounts," "organizations," "workspaces," or similar. This is a common pattern in many SaaS applications: users have a single login joined to multiple tenants to represent their membership within each. - -You use tenants in Knock to: - -- Support a user having a separate notification feed per tenant -- Apply per-tenant branding in emails -- Define per-tenant preference defaults that apply to all users within that tenant -- Apply per-user, per-tenant preferences -- πŸ”œ Power per-tenant template overrides - -## A conceptual model of tenants - -A tenant in Knock: - -- Is uniquely identified by an `id`, [per-environment](/concepts/variables). In most cases, - this `id` is the same `uuid` used to identify the tenant in your system -- Can have any number of custom properties -- Can store branding overrides and preference defaults -- Can be managed via the API - - - Behind the scenes, a tenant in Knock is really just another{" "} - Object in a special-system defined - collection, $tenants. That means that anything you can do on - an object you can do on a tenant. - - } -/> - -By default, Knock will create a stub tenant object for all unique tenants that you trigger a workflow run for. You can also use the [tenant APIs](/api-reference/tenants) to create and manage tenant objects from your system to Knock. - -## Associating workflow runs with a tenant - -It's important to note that tenants **do not** have a relationship to the -[users](/concepts/users) and [objects](/concepts/objects) you've identified in Knock. -That means Knock does not know _which tenant_ to associate with the set of users -you're triggering a notification for. Instead, you must explicitly tell Knock as -part of a workflow trigger to associate the workflow runs with a tenant. - - - -Tenants have a loose coupling to your users so Knock does not need to know -anything about the roles and permissions model associated with your product. This -means you have less data to synchronize to Knock and reduces the risk of drift -between what's current in your system and what's reflected in Knock. If you need -to model groups or lists of users, you can use our -[subscriptions model](/concepts/subscriptions) to do that. - -Once a workflow run has been triggered with a `tenant`, the Knock workflow engine will do the following: - -- Find the tenant or create an empty `tenant` object if one does not exist -- Expose that tenant object to the workflow run scope as a `tenant` variable -- Associates all messages produced in the workflow run with the tenant -- Applies any branding overrides to templates rendered -- Applies any preference defaults to the recipient's preference set -- Fetches any recipient-specific tenant preference sets - -## Using tenant data in a workflow run - -The full tenant object will be exposed, including any custom properties, in the workflow run scope under the `tenant` [namespace](/template-editor/variables#tenant). - -You can then use the tenant in a workflow to: - -- Add per-tenant-specific template changes, like custom messages or details. -- Create per-tenant conditions to only trigger steps for particular tenants. - -```markdown title="Using tenant data in a notification template" -# Hello from {{ tenant.name }} - -This is a message directly from {{ tenant.name }} going to {{ recipient.email }}. -``` - -## Syncing tenant data to Knock - -To get tenant data into Knock, we expose [various tenant-specific API methods](/api-reference/tenants). These methods make it possible to create or update a tenant, including any custom properties associated and any tenant settings, which include branding overrides and default preference sets. - - - -### Required attributes - -| Property | Description | -| -------- | ---------------------------------------- | -| `id` | A string to uniquely identify the tenant | - -### Optional attributes - -| Property | Description | -| ---------- | ----------------------------------------------------- | -| `name` | An optional name to associate with the tenant | -| `*` | Any custom properties you wish to store on the tenant | -| `settings` | A `TenantSettings` object to apply (see below) | - - - In Knock's Python SDK v1.0+, the optional name property and any custom properties provided must be passed in via the extra_body parameter. See the Python SDK documentation for details. - - - -} -/> - -### `TenantSettings` - -| Property | Description | -| --------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `branding.primary_color` | A hex value for the primary color | -| `branding.primary_color_contrast` | A hex value for the contrasting color to use with the primary color user | -| `branding.logo_url` | A fully qualified URL for an image to use as the logo of this tenant | -| `branding.icon_url` | A fully qualified URL for an image to use as the icon of this tenant | -| `preference_set` | A complete `PreferenceSet` to use as a default for all recipients with workflows triggered for this tenant | - -## Messages and tenants - -When a workflow is triggered with a `tenant` property, all of the -[Messages](/concepts/messages) produced in the workflow run will be tagged with -the `id` of the tenant. - -Tagging messages by the tenant makes it possible to query for tenant-specific -messages in both the API and the dashboard. We also expose this behavior for -in-app feed messages, making it possible to expose per-user, per-tenant feeds -([see below for additional information](#scoping-in-app-feeds) on this use case). - -## Working with tenants in the Knock dashboard - -Tenant data is also exposed in the Knock dashboard. From the dashboard it's possible to: - -- View information about specific tenants, including custom properties set -- View message logs of messages generated that were associated with the tenant -- View workflow run information for all runs associated with the tenant -- View and set custom branding settings -- View default preferences set for a tenant - -You can find tenant information on the **Tenants** page under the **Recipients** section in the main sidebar of the dashboard. - -## Using tenants - -### Scoping in-app feeds - -Multi-tenancy is important in your notification system when handling in-app feeds. Lets look at an example. -Imagine that we have a SaaS application, Collaborato, where our users belong to one or more different -workspaces. When one of our users is active in a current workspace, we want to make sure they only -see notifications that are relevant for that workspace. That is, a user in the "Acme Fish Co." workspace -should only see notifications that are relevant to "Acme Fish Co." - -#### Example - -To support this use case within Knock, we can pass a `tenant` identifier into our trigger calls. This `tenant` does not have to be configured in any way beforehand, it can simply be a unique identifier you choose to represent this group. - - - -When retrieving our feed to be displayed, we can then scope the feed to only show items relevant -to the tenant: - -```jsx title="Client-side feed scoping" -// If you're using our `client-js` SDK: -import Knock from "@knocklabs/client"; -const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); - -const feedClient = knockClient.feeds.initialize( - process.env.KNOCK_FEED_CHANNEL_ID, - { - // Scope all requests to the current workspace - tenant: currentWorkspace.id, - }, -); - -// Or if you're using the React SDK: - - ... -; -``` - -By providing the `tenant` property here, we're letting Knock know that the notifications produced -in the `trigger` call belong to a particular tenant and when we're showing the feed to our customers we **only** want to see the feed that's related to that tenant. - -Under the hood Knock will ensure that the badge counts you receive for the feed will be relevant -only to the active workspace, and that no real-time notifications will be received for any messages -that aren't relevant to the user. - -### Custom branding - - - Per-tenant branding is only available on our{" "} - Enterprise plan. - - } -/> - -You can use tenants to define default branding settings when sending email notifications that override your account-level brand settings. When you trigger a workflow with a `tenant`, it will use any settings defined on that tenant in place of the account-level brand settings to style your email layout steps. - -#### Example - -Let’s say you’re a hospitality company and own two boutique hotels, β€œThe Black Lodge” and β€œThe Great Northern.” Both want custom branding for their reservation update emails. - -First, we’ll want to add both of these hotels as `Tenants`. Navigate to the **Tenants** page under the **Recipients** section on the main sidebar of your dashboard and click β€œCreate tenant." There, you’ll add a name and unique ID by which you'll reference the tenant when triggering notifications. You can also upload a logo, an icon, and select primary colors directly from the interface here. - -Now that the tenant is set up, when you trigger a workflow with an email step you can pass the ID for one of these tenants. It will override the account branding settings with the settings you configured for your tenant. If you want to send a reservation reminder to the guests of The Black Lodge, you can pass the ID you set for that hotel, `black-lodge`, into the tenant field of the workflow trigger option to override default account settings with those you've created for this tenant. - - - -### Per-tenant user preferences and tenant preference defaults - - - Per-tenant user preferences and tenant preference defaults are only - available on our Enterprise plan. - - } -/> - -Another advanced tenancy use case is managing different sets of preferences for each user-tenant pair. That is, a user may have different preferences configured for "Acme Fish Co." than they do for "Bell's Bagels," two hypothetical workspaces within our example collaboration app, Collaborato. - -We also support the ability to set per-tenant defaults, where an admin in a tenant within your product can set the default preferences for all users within that tenant. - -You can learn more about how to set per-tenant preferences and tenant preference defaults in [our preferences documentation](/preferences/tenant-preferences). - -## Frequently asked questions - - - - There are no limits associated with tenants. - - - Yes, you can still use our APIs to work with tenant data, and trigger workflow runs for specific tenants. However, per-tenant preferences and custom branding are features gated for enterprise plans only. - - - Knock does not know anything about the mapping between your users and your tenant entities, meaning you do not need to map user permissions. - - - Absolutely, you can use a tenant as a `recipient` or `actor` in a workflow trigger by referencing it as an object with the structure `{ collection: "$tenants", id: "tenant-id" }`. - - - Yes, you can subscribe recipients to a tenant by setting the collection of the object to subscribe to as `$tenants` and using the `id` of the tenant as the object id. - - - We're currently working on this feature to create per-tenant template overrides at the workflow step level. If you're interested in being an early adopter of this feature, or this is blocking your adoption of Knock, [please get in touch](mailto:support@knock.app?subject=Per-tenant%20templates). - - - While it's technically possible to create per-tenant workflows in Knock, we recommend not doing this where possible and opting to use our step conditions, preferences, and per-tenant templates to provide the customizations you need. The reason is creating and managing per-tenant workflows increases the surface area of the number of notifications you need to support, and more commonly what we've found from working with customers is there are more similarities between per-customer workflows than differences, which can usually be encapsulated in our workflow model. - - If you find that you have different needs here, we'd love to speak with you. Please [get in touch](mailto:support@knock.app) and we can arrange a consultation with a notification support specialist on the Knock team to walk through your use case. - - - - No, today it's only possible to have a single-level of hierarchy for your tenants. If you need to apply deeper hierarchy to your tenant objects, please [get in touch](mailto:support@knock.app) and we can discuss your use case further. - - diff --git a/content/concepts/users.mdx b/content/concepts/users.mdx index e1fac7b87..1b6bbf52d 100644 --- a/content/concepts/users.mdx +++ b/content/concepts/users.mdx @@ -45,13 +45,13 @@ The following attributes are required for each user you identify with Knock. The following attributes are optional, depending on the channel types you decide to use with Knock. -| Property | Description | -| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| email | The primary email address for the user (required for email channels) | -| name | The full name of the user | -| avatar | A URL for the avatar of the user | -| phone_number | The [E.164](https://www.twilio.com/docs/glossary/what-e164) phone number of the user (required for SMS channels) | -| timezone | A valid [tz database time zone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (optional for [recurring schedules](/concepts/schedules#scheduling-workflows-with-recurring-schedules-for-recipients)) | +| Property | Description | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| email | The primary email address for the user (required for email channels) | +| name | The full name of the user | +| avatar | A URL for the avatar of the user | +| phone_number | The E.164 phone number of the user (required for SMS channels) | +| timezone | A valid tz database time zone string (optional for [recurring schedules](/concepts/schedules#scheduling-workflows-with-recurring-schedules-for-recipients)) | ### Storing user properties diff --git a/content/concepts/workflows.mdx b/content/concepts/workflows.mdx index 8fd7c47d5..7e383601e 100644 --- a/content/concepts/workflows.mdx +++ b/content/concepts/workflows.mdx @@ -150,7 +150,7 @@ You can learn more about automating workflow management in the [Knock CLI refere While it's possible to create per-customer workflows using the management API, we recommend avoiding doing this in favor of using [per-tenant - overrides](/concepts/tenants#custom-branding) and + branding](/multi-tenancy/per-tenant-branding) and [preferences](/concepts/preferences) to control individual workflows. diff --git a/content/designing-workflows/ai-agent-function.mdx b/content/designing-workflows/ai-agent-function.mdx index 325ea6e88..247c7521b 100644 --- a/content/designing-workflows/ai-agent-function.mdx +++ b/content/designing-workflows/ai-agent-function.mdx @@ -1,25 +1,19 @@ --- -title: AI agent function -description: Learn how to use the AI agent workflow function to enrich data and personalize messaging in Knock workflows. +title: Agent function +description: Learn how to use the agent workflow function to enrich data and personalize messaging in Knock workflows. tags: ["steps", "AI", "agent", "LLM", "functions", "personalization"] section: Designing workflows --- - - AI agent function is currently in beta. If you'd like early access, or if - this is blocking your adoption of Knock, please{" "} - - get in touch - - . - - } +A workflow with an agent step that qualifies sign-ups, showing the agent configuration and response -The AI agent function runs a prompt on an AI model of your choice and makes the response available in your workflow state. You can use it to enrich recipient data, personalize messaging, and bring AI-powered context into your messaging flows. +The agent function runs a prompt on an AI model of your choice and makes the response available in your workflow state. You can use it to enrich recipient data, personalize messaging, and bring AI-powered context into your messaging flows. Common use cases include: @@ -29,7 +23,7 @@ Common use cases include: ## How it works -When a workflow run reaches an AI agent step, Knock: +When a workflow run reaches an agent step, Knock: 1. Renders your prompt with the current [workflow run scope](/concepts/conditions#condition-types) (recipient, actor, tenant, data, etc.). 2. Sends the prompt to the AI model you've selected. @@ -37,7 +31,7 @@ When a workflow run reaches an AI agent step, Knock: The response is stored as `data.`. You can reference this data in subsequent steps and templates. -## Configuring an AI agent step +## Configuring an agent step ### Selecting a model @@ -77,9 +71,9 @@ See the [Knock template editor reference](/template-editor/overview) for more on ### Response format -By default, the AI agent returns a single string response available as `data..text`. +By default, the agent returns a single string response available as `data..text`. -You can set the response format to **JSON** when you need structured output for use in templates or [branch steps](/designing-workflows/branch-function). When using JSON format, you must supply a JSON schema for the shape of the data that you'd like the AI agent to fill in. +You can set the response format to **JSON** when you need structured output for use in templates or [branch steps](/designing-workflows/branch-function). When using JSON format, you must supply a JSON schema for the shape of the data that you'd like the agent to fill in. ```json title="Example JSON schema" { @@ -109,55 +103,65 @@ When **web search** is enabled, the agent can use a browser to crawl pages and g Web search increases the credit cost of each step execution. -## Testing AI agent steps +## Testing agent steps -You can run test executions of your AI agent step from the workflow editor. Test runs **do not consume credits**. +You can run test executions of your agent step from the workflow editor. Test runs **do not consume credits**. -1. Open the AI agent step in the workflow editor. +1. Open the agent step in the workflow editor. 2. Click the test button next to the prompt field. 3. Specify the trigger parameters (actor, recipient, trigger data, tenant) for the test run. 4. Click **Run test**. -The test runner executes only the AI agent step in isolation. If your step expects data from a preceding step (such as a batch or fetch), include that data in the **Data** field when running the test. +The test runner executes only the agent step in isolation. If your step expects data from a preceding step (such as a batch or fetch), include that data in the **Data** field when running the test. ## Credits and billing -AI agent function executions consume **AI agent credits**. Credits are used when the step successfully runs in a workflow. Test runs do not consume credits. +Agent function executions consume **agent credits**. Credits are used when the step successfully runs in a workflow. Test runs do not consume credits. The credit cost per execution depends on: - **Model.** Each model has a different credit cost per run. -- **Web search.** Enabled by default; adds credits per execution. -- **Thinking.** When enabled, adds credits for extended reasoning. +- **Web search.** Adds credits per execution. +- **Input/output tokens.** The number of tokens in the prompt and response. + +The credit cost you're charged will either be the minimum cost of the model (and web search, if enabled) or the actual cost of the input/output tokens, whichever is greater. ### Managing credits - **Included credits.** Your plan includes a set amount of credits per billing period. Credits do not roll over. - **Purchasing credits.** When you need more, go to the **Billing** page in your account settings to purchase additional credits. - **Auto-purchase.** You can configure a threshold and amount for automatic credit top-ups when your balance falls below a certain level. -- **Running out of credits.** When you run out of credits, AI agent steps halt. You can configure behavior when this happens (e.g. continue the workflow or stop the workflow). +- **Running out of credits.** When you run out of credits, agent steps halt. You can configure behavior when this happens (e.g. continue the workflow or stop the workflow). ### Credit reference -When web search is enabled, the credit cost is increased by 10 credits per execution. +When web search is enabled, the minimum credit cost is increased by 3 credits per execution. + +### Understanding credit use + +You can see the minimum credit costs for a selected model in the agent function in the workflow editor. + +When you run a test execution of the agent step, you can see the **actual credit cost** for the execution that your account would have been charged. + +In the workflow debugger, you can see the actual credit cost for each step execution under the **credits** column. ## Error handling -When an AI agent step fails (e.g. model error, timeout, or invalid response), Knock marks the step as failed. You can configure whether the workflow should halt or continue to the next step when an AI agent step fails using the "Halt on error" setting. +When an agent step fails (e.g. model error, timeout, or invalid response), Knock marks the step as failed. You can configure whether the workflow should halt or continue to the next step when an agent step fails using the "Halt on error" setting. -The AI agent step will retry up to 3 times for certain types of errors: +The agent step will retry up to 3 times for certain types of errors: - **Model errors.** The model returns an error response. - **Timeout errors.** The request takes longer than the model's timeout. @@ -165,9 +169,9 @@ The AI agent step will retry up to 3 times for certain types of errors: Note: we will **not** retry when the model returns an error response or indicates that they could not fulfill the request. -## Debugging AI agent steps +## Debugging agent steps -You can use the [workflow run logs](/send-notifications/debugging-workflows) to debug AI agent steps. For each AI agent step, the logs include: +You can use the [workflow run logs](/send-notifications/debugging-workflows) to debug agent steps. For each agent step, the logs include: - The rendered prompt sent to the model. - The model response. @@ -177,14 +181,14 @@ You can use the [workflow run logs](/send-notifications/debugging-workflows) to ## Frequently asked questions - - Yes. The output from an AI agent step is available in the workflow run scope, so you can use it in [step conditions](/designing-workflows/step-conditions) on subsequent steps. + + Yes. The output from an agent step is available in the workflow run scope, so you can use it in [step conditions](/designing-workflows/step-conditions) on subsequent steps. - For example, you could branch based on whether the AI agent returned a specific persona type or a non-empty enrichment result. + For example, you could branch based on whether the agent returned a specific persona type or a non-empty enrichment result. - - Yes. You can place an AI agent step after a [batch function](/designing-workflows/batch-function) to summarize or enrich the batched activities. The AI agent has access to the batch data in the workflow run scope. + + Yes. You can place an agent step after a [batch function](/designing-workflows/batch-function) to summarize or enrich the batched activities. The agent has access to the batch data in the workflow run scope. If the prompt fails to render due to a Liquid error, the step will fail and the workflow will halt (or continue, depending on your error handling configuration). Use the [workflow debugger](/send-notifications/debugging-workflows) to inspect the error. diff --git a/content/designing-workflows/batch-function.mdx b/content/designing-workflows/batch-function.mdx index aa00b0c56..8552f3093 100644 --- a/content/designing-workflows/batch-function.mdx +++ b/content/designing-workflows/batch-function.mdx @@ -88,7 +88,7 @@ You can also set the length of your batch windows dynamically using a variable. When specifying a dynamic batch window you must provide one of the following: -- An [ISO-8601 timestamp](https://en.wikipedia.org/wiki/ISO_8601) (e.g. `2022-05-04T20:34:07Z`) which must be a datetime in the future +- An ISO-8601 timestamp (e.g. `2022-05-04T20:34:07Z`) which must be a datetime in the future - A relative duration (e.g `{ "unit": "seconds", "value": 30 }`) - A window rule (e.g `{ "frequency": "daily", "hours": 9, "minutes": 30 }`) @@ -296,7 +296,10 @@ Here's a list of the variables that you can use to work with batch-related state text={ <> The render limit setting for batch activities and actors is only available - on our Enterprise plan. + on our{" "} + + Enterprise plan. + } /> diff --git a/content/designing-workflows/channel-step.mdx b/content/designing-workflows/channel-step.mdx index 45ec06f0c..2217ec415 100644 --- a/content/designing-workflows/channel-step.mdx +++ b/content/designing-workflows/channel-step.mdx @@ -20,6 +20,8 @@ When a channel step is executed Knock does the following: If the step continues, Knock will render [the template](/template-editor) associated with the step and enqueue a message to [deliver to the provider](/send-notifications/delivering-notifications) via the configured credentials on the channel. +See our [debugging workflows documentation](/send-notifications/debugging-workflows#understanding-workflow-execution-behavior) for more details on how workflow steps are executed and what happens when they encounter errors. + ## Channel support You can read more about configuring channels in our [integrations overview](/integrations/overview). diff --git a/content/designing-workflows/delay-function.mdx b/content/designing-workflows/delay-function.mdx index d9da65df2..7d3dc43a6 100644 --- a/content/designing-workflows/delay-function.mdx +++ b/content/designing-workflows/delay-function.mdx @@ -22,7 +22,7 @@ You can also set the length of your delay dynamically using a variable. You can When specifying a dynamic delay period you must provide one of the following: -- An [ISO-8601 timestamp](https://en.wikipedia.org/wiki/ISO_8601) (e.g. `2022-05-04T20:34:07Z`) which must be a datetime in the future +- An ISO-8601 timestamp (e.g. `2022-05-04T20:34:07Z`) which must be a datetime in the future - A duration unit (e.g `{ "unit": "seconds", "value": 30 }`) - A window rule (e.g `{ "frequency": "daily", "hours": 9, "minutes": 30 }`) diff --git a/content/designing-workflows/overview.mdx b/content/designing-workflows/overview.mdx index 8d15db6e7..740d548b2 100644 --- a/content/designing-workflows/overview.mdx +++ b/content/designing-workflows/overview.mdx @@ -53,7 +53,7 @@ You can add any of the major [channel types supported by Knock](/integrations/ov ### Function steps -A function is a step in a workflow that does something to the data being passed in your trigger call. You can use functions by entering the workflow builder and adding function steps onto the canvas. +A function is a step in a workflow that does something to the data being passed in your trigger call. You can use functions by entering the workflow builder and adding function steps onto the canvas. In the workflow builder, functions are grouped in tabs. The **Data functions** tab includes steps that update data in Knock during a workflow run. We currently support the following functions: @@ -66,6 +66,14 @@ We currently support the following functions: - [Throttle](/send-notifications/designing-workflows/throttle-function) (limits the number of executions of the workflow for the recipient over a window of time) - [Trigger workflow](/send-notifications/designing-workflows/trigger-workflow-function) (execute a nested workflow with trigger data derived from parent workflow data and environment variables) +**Data functions** + +- [Update user](/send-notifications/designing-workflows/update-user-function) (update a user's properties in Knock during a workflow run) +- [Update tenant](/send-notifications/designing-workflows/update-tenant-function) (update a tenant's properties in Knock during a workflow run) +- [Update object](/send-notifications/designing-workflows/update-object-function) (update an object's properties in Knock during a workflow run) +- [Update data](/send-notifications/designing-workflows/update-data-function) (update workflow data state with computed or static values) +- [Update audience](/send-notifications/designing-workflows/update-audience-function) (add or remove the current recipient from a static audience) + ## Step conditions Each workflow step can have one or more conditions that determine, at workflow execution time, if the step should execute. Conditions are one way you can add control flow logic to your notification workflows. diff --git a/content/designing-workflows/throttle-function.mdx b/content/designing-workflows/throttle-function.mdx index 76d654358..34bc13b3e 100644 --- a/content/designing-workflows/throttle-function.mdx +++ b/content/designing-workflows/throttle-function.mdx @@ -33,7 +33,7 @@ You can also set the length of your throttle windows dynamically using a variabl When specifying a dynamic window you must provide one of the following: -- An **[ISO-8601 timestamp](https://en.wikipedia.org/wiki/ISO_8601)** (e.g. `2022-05-04T20:34:07Z`) which must be a datetime in the future +- An ISO-8601 timestamp (e.g. `2022-05-04T20:34:07Z`) which must be a datetime in the future - A relative duration unit (e.g `{ "unit": "seconds", "value": 30 }`) - A window rule (e.g `{ "frequency": "daily", "hours": 9, "minutes": 30 }`) diff --git a/content/designing-workflows/trigger-workflow-function.mdx b/content/designing-workflows/trigger-workflow-function.mdx index 5b44c4ea7..89325077c 100644 --- a/content/designing-workflows/trigger-workflow-function.mdx +++ b/content/designing-workflows/trigger-workflow-function.mdx @@ -5,20 +5,6 @@ tags: ["steps", "functions"] section: Designing workflows --- - - Trigger workflow function is currently in beta. If you'd like early - access, or this is blocking your adoption of Knock, please{" "} - - get in touch - - . - - } -/> - A trigger workflow function enables you to invoke a workflow from within another workflow. This function allows you to compose complex notifications by reusing logic across multiple workflows, improving maintainability and reducing duplication. When using the trigger workflow function, you can utilize the data passed directly from the parent workflow or specify custom data for use when triggering the nested workflow. diff --git a/content/designing-workflows/update-audience-function.mdx b/content/designing-workflows/update-audience-function.mdx new file mode 100644 index 000000000..668597452 --- /dev/null +++ b/content/designing-workflows/update-audience-function.mdx @@ -0,0 +1,95 @@ +--- +title: Update audience function +description: Learn how to add or remove users from a static audience during a workflow run. +tags: ["steps", "audiences", "data", "functions"] +section: Designing workflows +--- + +An update audience function adds or removes a user from a [static audience](/concepts/audiences). + +You can add or remove users from a static audience to trigger other workflows, send [broadcasts](/concepts/broadcasts), and make the recipient eligible to see [guides](/concepts/guides). + +Common use cases include making a user eligible for a guide after they complete an onboarding step, removing a user from a promotional audience after they convert, or syncing audience membership based on data available in your workflow trigger. + +## Choosing the action + +The update audience step supports two actions: + +- **Add to.** Adds the current recipient to the selected static audience. If the recipient is already a member, the step succeeds with no change. +- **Remove from.** Removes the current recipient from the selected static audience. If the recipient is not a member, the step succeeds with no change. + +
+ +
+ Update audience function step in the workflow builder. +
+
+ +## Selecting the audience + +Choose a static audience from the audiences available in your environment. The dropdown lists all static audiences that have been created and committed in the current environment. + +Once you select an audience, the step shows usages of the selected audience, including workflows, broadcasts, and guides. + +## Limitations + +The update audience step only works with [static audiences](/concepts/audiences). + +The step always operates on the current workflow user. + +An update audience step cannot Add to the same audience that triggers the workflow. If your workflow uses an [audience change trigger](/send-notifications/triggering-workflows/audiences), the trigger audience is not available in any update audience steps within that workflow. Likewise, if a workflow already contains an update audience step Adding to a given audience, that audience cannot be selected as the trigger audience. + +## Debugging + +You can use the [workflow run logs](/send-notifications/debugging-workflows) to debug update audience steps. When viewing a workflow run for a specific recipient, select the update audience step to see whether the recipient was added to or removed from the audience, and the audience that was targeted. + +
+ +
+ Viewing an update audience step in the workflow run logs. The recipient was + added to the audience. +
+
+ +
+ +
+ Viewing an update audience step in the workflow run logs. The recipient was + not removed from the audience because they were not a member. +
+
+ +## Frequently asked questions + + + + Use an **update audience step** when audience membership should change as part of a workflow run, for example adding a user to a "completed onboarding" audience after they finish a setup flow. The step runs inline with your workflow so subsequent steps immediately see the updated membership. + + Use the [audiences API](/api-reference/audiences) when you need to sync audience membership in bulk from an external source (such as a data warehouse or reverse ETL tool), or when the membership change is not tied to a specific workflow run. + + + + No. Dynamic audience membership is computed automatically from user properties. To make a user eligible for a dynamic audience during a workflow, use an [update user step](/send-notifications/designing-workflows/update-user-function) to set a property that matches the audience's query rules. + + + No. The update audience step always operates on the current workflow recipient. If you need to modify audience membership for a different user, use the [audiences API](/api-reference/audiences) instead. + + diff --git a/content/designing-workflows/update-data-function.mdx b/content/designing-workflows/update-data-function.mdx new file mode 100644 index 000000000..211ab4af7 --- /dev/null +++ b/content/designing-workflows/update-data-function.mdx @@ -0,0 +1,128 @@ +--- +title: Update data function +description: Learn more about the update data function within Knock's notification engine. +tags: ["steps", "data", "workflow state", "functions", "liquid"] +section: Designing workflows +--- + +An update data function updates properties on the workflow `data` state as a step in a workflow. Any data set by an update data function is merged into the original trigger `data` provided on the workflow trigger and made immediately available to all subsequent steps in the workflow. + +## Configuring data properties + +To configure an update data function, you can add key-value pairs in the step editor or use the code editor to input a JSON object directly. Each key represents a property name that will be set on the workflow data, and each value can be either a static value or a Liquid expression. + +Each value field is a Liquid-compatible input. This means you can use Liquid variables and control flow to inject variable data, access Knock-controlled workflow state attributes (e.g., `recipient`), and dynamically compute values per workflow run. + +
+ +
Update data function step in the workflow builder.
+
+ +
+ +
+ Code editor mode for configuring an update data function. +
+
+ +Here are some example configurations: + +| Key | Value | Description | +| ---------------- | -------------------------------------------- | --------------------------------------------------- | +| `full_name` | `{{ data.first_name }} {{ data.last_name }}` | Combines first and last name into a single property | +| `greeting` | `Hello` | Sets a static greeting value | +| `item_count` | `{{ data.items \| size }}` | Computes the count of items in an array | +| `is_premium` | `{{ recipient.plan \| equals: "premium" }}` | Sets a boolean flag based on recipient data | +| `formatted_date` | `{{ "now" \| date: "%B %d, %Y" }}` | Adds the current date in a specific format | + +See the [Knock template editor reference](/template-editor/overview) for detailed information on working with Liquid in your Knock message templates. + +## Merging data + +When an update data function executes, Knock will merge the configured properties into the `data` you originally passed to [the workflow trigger call](/send-notifications/triggering-workflows). Knock uses a shallow-merge strategy where: + +- Top-level keys from the update data step are merged into the existing workflow run data, adding new keys and replacing existing ones. +- Nested objects are replaced entirely rather than deep-merged. If the original data has a nested object and the update data step sets the same key, the entire nested object is overwritten. + +_The merged data result from an update data function step then becomes the global trigger data for all subsequent steps in the workflow run._ + +The example below illustrates how this could look in practice. + +```json title="Example data merge for update data function steps" +// Original trigger data +{ + "first_name": "Jane", + "last_name": "Doe", + "metadata": { + "source": "web" + } +} + +// Update data function configuration +// full_name: {{ data.first_name }} {{ data.last_name }} +// metadata: { "computed": true } + +// Merge result +{ + "first_name": "Jane", + "last_name": "Doe", + "full_name": "Jane Doe", + "metadata": { + "computed": true + } +} +``` + +## Debugging update data functions + +You can use the [workflow debugger](/send-notifications/debugging-workflows) to debug your update data function steps. When viewing a workflow run for a specific recipient, you can select the workflow data function step to see the computed data that was merged into your workflow run state. + +
+ +
+ Viewing update data function step in the workflow run logs. +
+
+ +## Frequently asked questions + + + + Use an **update data function** when you need to transform, compute, or restructure data that is already available in your workflow run. This is ideal for operations like combining fields, setting defaults, or pre-computing Liquid expressions. + + Use a **[fetch function](/send-notifications/designing-workflows/fetch-function)** when you need to retrieve additional data from an external service that isn't available in your trigger data. The fetch function makes an HTTP request to your service and merges the response into your workflow data. + + In some cases, you might use both: a fetch function to retrieve additional data, followed by an update data function to transform or restructure the combined data for your templates. + + + + You can set a top-level property to an object value, but you cannot directly + set deeply nested properties (e.g., `metadata.nested.value`). If you need to + update a nested property, you'll need to set a new value for the entire parent object (`metadata`), which + will replace any existing nested data due to the shallow-merge behavior. See the section on [merging data](#merging-data) above for an example. + + + If a Liquid expression in your update data function encounters an error (such as referencing a property that doesn't exist), **the step will fail and halt your workflow run.** You can use Liquid's default filter (e.g., `{{ data.optional_field | default: "fallback" }}`) to provide fallback values and avoid errors when data may be missing. + + + Yes. Since each update data function merges its computed data into the workflow's global data state, subsequent steps (including other update data functions) can reference that data using the `data` namespace. For example, if a previous step set `data.full_name`, a later step can reference it as `{{ data.full_name }}`. + + diff --git a/content/designing-workflows/update-object-function.mdx b/content/designing-workflows/update-object-function.mdx new file mode 100644 index 000000000..629c0021b --- /dev/null +++ b/content/designing-workflows/update-object-function.mdx @@ -0,0 +1,139 @@ +--- +title: Update object function +description: Learn how to update an object's properties in Knock during a workflow run. +tags: ["steps", "objects", "data", "functions"] +section: Designing workflows +--- + +An update object function updates an [object](/concepts/objects) in a collection stored in Knock as a step in your workflow. Use it when you need to change an object's properties based on workflow context to enable downstream steps or other workflows to use the updated data. + +Common use cases include syncing object state from your trigger (for example, status or last activity), or storing data on the object that you reference later when the object is used as a recipient in another workflow. + +## Selecting the collection and object + +The update object step does **not** support a "current object" mode. You must specify which object to update. + +- **Collection.** Choose from all [object collections](/concepts/objects) in your environment (for example, "Projects" or "Repositories"). +- **Object ID.** Select a property that resolves to the object ID within that collection, or use a literal object ID. This can come from your trigger data (for example, `data.project_id`), from the recipient when the recipient is an object (`recipient.id`), or from another source in the workflow run. + +The update object step supports upserting. If you specify an object ID that does not yet exist in that collection in Knock, the object will be created with the properties you set. + + + +
+ +
Update object function step in the workflow builder.
+
+ +
+ +
+ Configuring collection and object ID for the update object step. +
+
+ +## Updating preferences + +You can update an object's [preferences](/preferences/overview) from within a workflow. Set them using the `preferences` key. The value should follow the structure of a [`PreferenceSet`](/api-reference/recipients/preferences/schemas/preference_set). + +Preferences are deep-merged into the object's existing preference set. This means you can set individual preference keys across multiple update object steps without overwriting previously set values. For example, two sequential update object steps can each target a different part of the preference set and the results will be combined: + +```json title="Preference deep merge across two update object steps" +// Update object step 1 +{ + "preferences": { "default": { "categories": { "admin": false } } } +} + +// Update object step 2 +{ + "preferences": { "default": { "channel_types": { "email": false } } } +} + +// Resulting preferences on the object +{ + "default": { + "categories": { "admin": false }, + "channel_types": { "email": false } + } +} +``` + +## Configuring properties + +You can set any number of properties on the object. Each property has a **key** and a **value**. + +- **Key.** The property name. You can use an existing object property or define a new property. +- **Value.** The value to set. You can use [Liquid](/template-editor/overview) (for example, `{{ data.status }}` or `{{ recipient.name }}`) to resolve a dynamic value from workflow data, the recipient, the actor, or [variables](/concepts/variables). You can also use a static value (for example, `"active"` or `true`). + +Properties can be configured as a JSON blob or as key-value pairs. + +## Merging properties + +When an update object step executes, Knock deep-merges the configured properties into the existing object. This means: + +- Top-level keys are merged into the existing object, adding new keys and replacing existing ones. +- Nested objects are recursively merged rather than replaced. If the existing object has a nested object and the update step sets a key inside it, only that key is updated β€” the rest of the nested object is preserved. + +This is the same deep-merge behavior used by the [objects API](/api-reference/objects/set). + +```json title="Property deep merge across two update object steps" +// Update object step 1 +{ + "meta": { "first": "hello", "last": "world" } +} + +// Update object step 2 +{ + "meta": { "last": "knock" } +} + +// Resulting properties on the object +{ + "meta": { + "first": "hello", + "last": "knock" + } +} +``` + +## Limitations + +Update object steps do not support updating [channel data](/managing-recipients/setting-channel-data). You cannot set or change channel data on an object via this step. + +In addition, `channel_data` is not rendered in the workflow event success or error logging for these recipients, even when channel data is present on the object. + +## Debugging + +You can use the [workflow run logs](/send-notifications/debugging-workflows) to debug update object steps. The logs render the full before/after diff view of the object so that you can verify the pre-state, the properties that were set, and the post-state. + +
+ +
+ Before/after diff for an update object step in the workflow run logs. This + workflow run upserted a new object, so the diff shows only the new version. +
+
+ +Update object steps are also available in the [management API](/mapi-reference/overview). diff --git a/content/designing-workflows/update-tenant-function.mdx b/content/designing-workflows/update-tenant-function.mdx new file mode 100644 index 000000000..2fc2b1bae --- /dev/null +++ b/content/designing-workflows/update-tenant-function.mdx @@ -0,0 +1,129 @@ +--- +title: Update tenant function +description: Learn how to update a tenant's properties in Knock during a workflow run. +tags: ["steps", "tenants", "data", "functions"] +section: Designing workflows +--- + +An update tenant function updates a [tenant](/concepts/tenants) stored in Knock as a step in your workflow. Use it when you need to change a tenant's properties based on workflow context to enable downstream steps or other workflows to use the updated data. + +Common use cases include syncing tenant state from your trigger (for example, approval status or feature flags), updating [tenant-level preference defaults](/multi-tenancy/per-tenant-preferences), or storing data on the tenant that you reference later in the same workflow or in another workflow. + +## Selecting the tenant + +By default, Knock updates the tenant associated with the current workflow run (the `tenant` you passed in the [trigger](/send-notifications/triggering-workflows)). You can leave this default, or select a property that resolves to a tenant ID (or use a literal tenant ID) to update a different tenant. + +The update tenant step supports upserting. If you specify a tenant ID that does not yet exist in Knock, the tenant will be created with the properties you set. + +
+ +
Update tenant function step in the workflow builder.
+
+ +
+ +
Configuring a tenant ID for the update tenant step.
+
+ +## Updating preferences + +You can update a tenant's default [preferences](/preferences/overview) from within a workflow. Use the `preference_set` key nested under `settings` (not `preferences`) when setting tenant preferences. The value should follow the structure of a [`PreferenceSet`](/api-reference/recipients/preferences/schemas/preference_set). For more information on how tenant preferences work and how to structure them, see [per-tenant preferences](/multi-tenancy/per-tenant-preferences). + +```json title="Setting tenant preferences via the update tenant step" +{ + "settings": { "preference_set": { "categories": { "admin": false } } } +} +``` + +Tenant preference defaults are deep-merged into the tenant's existing preference set. This means you can set individual preference keys across multiple update tenant steps without overwriting previously set values. For example, two sequential update tenant steps can each target a different part of the preference set and the results will be combined: + +```json title="Preference deep merge across two update tenant steps" +// Update tenant step 1 +{ + "settings": { "preference_set": { "categories": { "admin": false } } } +} + +// Update tenant step 2 +{ + "settings": { "preference_set": { "channel_types": { "email": false } } } +} + +// Resulting tenant preference defaults +{ + "categories": { "admin": false }, + "channel_types": { "email": false } +} +``` + +For more details on updating tenant default preferences and how to modify the merge behavior, see the [per-tenant preferences FAQ](/multi-tenancy/per-tenant-preferences#frequently-asked-questions). + +## Configuring properties + +You can set any number of properties on the tenant. Each property has a **key** and a **value**. Properties can be configured as a JSON blob or as key-value pairs. + +- **Key.** The property name. You can use an existing tenant property or define a new property. +- **Value.** The value to set. You can use [Liquid](/template-editor/overview) (for example, `{{ data.tenant_name }}` or `{{ tenant.id }}`) to resolve a dynamic value from workflow data, the recipient, the actor, or [variables](/concepts/variables). You can also use a static value (for example, `"Acme Corp"` or `true`). + +## Merging properties + +When an update tenant step executes, Knock deep-merges the configured properties into the existing tenant. This means: + +- Top-level keys are merged into the existing tenant, adding new keys and replacing existing ones. +- Nested objects are recursively merged rather than replaced. If the existing tenant has a nested object and the update step sets a key inside it, only that key is updated β€” the rest of the nested object is preserved. + +```json title="Property deep merge across two update tenant steps" +// Update tenant step 1 +{ + "meta": { "first": "hello", "last": "world" } +} + +// Update tenant step 2 +{ + "meta": { "last": "knock" } +} + +// Resulting properties on the tenant +{ + "meta": { + "first": "hello", + "last": "knock" + } +} +``` + +## Limitations + +Update tenant steps do not support updating [channel data](/managing-recipients/setting-channel-data). You cannot set or change channel data on a tenant via this step. + +In addition, `channel_data` is not rendered in the workflow event success or error logging for these recipients, even when channel data is present on the tenant. + +## Debugging + +You can use the [workflow run logs](/send-notifications/debugging-workflows) to debug update tenant steps. The logs render the full before/after diff view of the tenant so that you can verify the pre-state, the properties that were set, and the post-state. + +
+ +
+ Before/after diff for an update tenant step in the workflow run logs. +
+
+ +Update tenant steps are also available in the [management API](/mapi-reference/overview). diff --git a/content/designing-workflows/update-user-function.mdx b/content/designing-workflows/update-user-function.mdx new file mode 100644 index 000000000..4e2bb95dc --- /dev/null +++ b/content/designing-workflows/update-user-function.mdx @@ -0,0 +1,127 @@ +--- +title: Update user function +description: Learn how to update a user in Knock during a workflow run. +tags: ["steps", "users", "data", "functions"] +section: Designing workflows +--- + +An update user function updates a [user](/concepts/users) stored in Knock as a step in your workflow. Use it when you need to change a user's properties or [preferences](/preferences/overview) based on workflow context to enable downstream steps, [audiences](/concepts/audiences), or other workflows to use the updated data. + +Common use cases include syncing state from your trigger (for example, "last notified at"), making a user eligible for a [dynamic audience](/concepts/audiences) so they'll receive a [guide](/concepts/guides), or updating notification preferences from within a workflow. + +## Selecting the user + +By default, Knock updates the current workflow [recipient](/concepts/recipients) (the user this run is for). Alternatively, you can select a property that resolves to a user ID (or use a literal user ID) to update a different user (for example, the `actor` or a user ID from your trigger `data`). + +The update user step supports upserting. If you specify a user ID that does not yet exist in Knock, a new user will be created with the properties you set. + +
+ +
Update user function step in the workflow builder.
+
+ +
+ +
Configuring a user ID for the update user step.
+
+ +## Updating preferences + +The update user step can update the user's notification [preferences](/preferences/overview). This enables you to create workflow logic that updates which channels or workflows a user is opted into. + +Set preferences using the `preferences` key on the user. The value should follow the structure of a [`PreferenceSet`](/api-reference/recipients/preferences/schemas/preference_set). + +Preferences are deep-merged into the user's existing preference set. This means you can set individual preference keys across multiple update user steps without overwriting previously set values. For example, two sequential update user steps can each target a different part of the preference set and the results will be combined: + +```json title="Preference deep merge across two update user steps" +// Update user step 1 +{ + "preferences": { "default": { "categories": { "admin": false } } } +} + +// Update user step 2 +{ + "preferences": { "default": { "channel_types": { "email": false } } } +} + +// Resulting preferences on the user +{ + "default": { + "categories": { "admin": false }, + "channel_types": { "email": false } + } +} +``` + +## Configuring properties + +You can set any number of properties on the user. Each property has a **key** and a **value**. Properties can be configured as a JSON blob or as key-value pairs. + +- **Key.** The property name. You can use an existing user property or define a new property. +- **Value.** The value to set. You can use [Liquid](/template-editor/overview) (for example, `{{ data.foo }}` or `{{ recipient.email }}`) to resolve a dynamic value from workflow data, the recipient, the actor, or [variables](/concepts/variables). You can also use a static value (for example, `"bar"` or `true`). + +## Merging properties + +When an update user step executes, Knock deep-merges the configured properties into the existing user. This means: + +- Top-level keys are merged into the existing user, adding new keys and replacing existing ones. +- Nested objects are recursively merged rather than replaced. If the existing user has a nested object and the update step sets a key inside it, only that key is updated β€” the rest of the nested object is preserved. + +This is the same deep-merge behavior used by the [identify API](/api-reference/users/identify). + +```json title="Property deep merge across two update user steps" +// Update user step 1 +{ + "meta": { "first": "hello", "last": "world" } +} + +// Update user step 2 +{ + "meta": { "last": "knock" } +} + +// Resulting properties on the user +{ + "meta": { + "first": "hello", + "last": "knock" + } +} +``` + +## Limitations + +Update user steps do not support updating [channel data](/managing-recipients/setting-channel-data). You cannot set or change channel data (for example, push tokens) via this step. + +In addition, `channel_data` is not rendered in the workflow event success or error logging for these recipients, even when channel data is present on the user. + +## Debugging + +You can use the [workflow run logs](/send-notifications/debugging-workflows) to debug update user steps. The logs render the full before/after diff view of the user so that you can verify the pre-state, the properties that were set, and the post-state. + +
+ +
+ Before/after diff for an update user step in the workflow run logs. +
+
+ +Update user steps are also available in the [management API](/mapi-reference/overview) and can be included when you show or upsert a workflow. diff --git a/content/developer-tools/agent-toolkit/getting-started.mdx b/content/developer-tools/agent-toolkit/getting-started.mdx index 0fe3c4835..9c01570b2 100644 --- a/content/developer-tools/agent-toolkit/getting-started.mdx +++ b/content/developer-tools/agent-toolkit/getting-started.mdx @@ -141,4 +141,4 @@ const llm = new ChatOpenAI({ - [Powering human-in-the-loop interactions](/developer-tools/agent-toolkit/human-in-the-loop-flows) - [Providing context](/developer-tools/agent-toolkit/providing-context) - [Building with LLMs](/developer-tools/building-with-llms) -- [Model Context Protocol (MCP) Server](/developer-tools/mcp-server) +- [Model Context Protocol (MCP) Server](/ai/mcp-server) diff --git a/content/developer-tools/agent-toolkit/human-in-the-loop-flows.mdx b/content/developer-tools/agent-toolkit/human-in-the-loop-flows.mdx index 9982414d5..894a2365e 100644 --- a/content/developer-tools/agent-toolkit/human-in-the-loop-flows.mdx +++ b/content/developer-tools/agent-toolkit/human-in-the-loop-flows.mdx @@ -135,7 +135,7 @@ This example shows you how to set up an approval flow that sends an in-app notif - To start, you'll want to setup a Knock workflow with a single, in-app feed channel step that sends an approval message to an in-app feed. You can find a workflow template for this in the [workflow templates repository](https://github.com/knocklabs/workflow-templates/tree/main/approve-tool-call). + To start, you'll want to setup a Knock workflow with a single, in-app feed channel step that sends an approval message to an in-app feed. You can find a workflow template for this in the workflow templates repository. Next, you'll need to render the in-app feed in your application. You can find steps for implementing this in the [building in-app feed documentation](/in-app-ui/react/feed). By default, your in-app feed will handle the interaction on the "Approve" and "Reject" buttons and send the `message.interacted` event to Knock. diff --git a/content/developer-tools/agent-toolkit/overview.mdx b/content/developer-tools/agent-toolkit/overview.mdx index 1ea7e6b2b..2181e28de 100644 --- a/content/developer-tools/agent-toolkit/overview.mdx +++ b/content/developer-tools/agent-toolkit/overview.mdx @@ -26,7 +26,7 @@ Knock Agent Toolkit exposes a set of tools that your AI agent applications can u To get started with the Knock agent toolkit, you must have: -- A [Knock account](https://dashboard.knock.app/signup) +- A Knock account - A [service token](/developer-tools/service-tokens) for your Knock account Once you have a Knock account and a service token, you can install the Knock agent toolkit using your preferred package manager. @@ -117,4 +117,4 @@ For example, the following permissions will expose only the `users` resource to - [Getting started with Agent Toolkit](/developer-tools/agent-toolkit/getting-started) - [Building with LLMs](/developer-tools/building-with-llms) -- [Model Context Protocol (MCP) Server](/developer-tools/mcp-server) +- [Model Context Protocol (MCP) Server](/ai/mcp-server) diff --git a/content/developer-tools/agent-toolkit/tools-reference.mdx b/content/developer-tools/agent-toolkit/tools-reference.mdx index 8fbacb990..4189f7a74 100644 --- a/content/developer-tools/agent-toolkit/tools-reference.mdx +++ b/content/developer-tools/agent-toolkit/tools-reference.mdx @@ -4,7 +4,7 @@ description: Learn more about the tools available in the Knock Agent Toolkit. section: Developer tools --- -The Knock Agent Toolkit exposes a suite of tools that your AI Agents can invoke to power cross-channel user messaging, without any integration logic. You can find the full list of available tools below. +The Knock Agent Toolkit exposes a suite of tools that your AI agents can invoke to power cross-channel user messaging, without any integration logic. You can find the full list of available tools below. ## Channels (`channels`) @@ -13,16 +13,17 @@ The Knock Agent Toolkit exposes a suite of tools that your AI Agents can invoke ## Commits (`commits`) - `listCommits`: List all commits in a single environment -- `commitAllChanges`: Commit all changes in an enviroinment +- `commitAllChanges`: Commit all changes in an environment - `promoteAllCommits`: Promote all commits from one environment to another ## Documentation (`documentation`) - `searchDocumentation`: Perform a search of the Knock documentation -## Email Layouts (`emailLayouts`) +## Email layouts (`emailLayouts`) - `listEmailLayouts`: List all email layouts in an environment +- `getEmailLayout`: Get an email layout by its key - `createOrUpdateEmailLayout`: Create or update an email layout in an environment ## Environments (`environments`) @@ -42,13 +43,16 @@ The Knock Agent Toolkit exposes a suite of tools that your AI Agents can invoke ## Messages (`messages`) +- `getMessage`: Get a message by its ID - `getMessageContent`: Get the content of a sent message +- `getMessageDeliveryLogs`: Get the delivery logs for a message +- `getMessageEvents`: Get the event timeline for a message ## Objects (`objects`) - `listObjects`: List all objects in a collection in an environment -- `createOrUpdateObject`: Create or update an object in a collection - `getObject`: Get an object in a collection by its id +- `createOrUpdateObject`: Create or update an object in a collection - `subscribeUsersToObject`: Subscribe one or more users to an object in a collection - `unsubscribeUsersFromObject`: Unsubscribe one or more users from an object in a collection @@ -61,14 +65,13 @@ The Knock Agent Toolkit exposes a suite of tools that your AI Agents can invoke ## Tenants (`tenants`) - `listTenants`: List all tenants in an environment -- `createOrUpdateTenant`: Create or update a tenant in an environment - `getTenant`: Get a tenant by its key +- `createOrUpdateTenant`: Create or update a tenant in an environment ## Users (`users`) -- `listUsers`: List all users in an environment -- `createOrUpdateUser`: Create or update a user in an environment - `getUser`: Get a user by their id +- `createOrUpdateUser`: Create or update a user in an environment - `getUserPreferences`: Get the preferences for a user - `setUserPreferences`: Set the preferences for a user - `getUserMessages`: Get the messages for a user diff --git a/content/developer-tools/building-with-llms.mdx b/content/developer-tools/building-with-llms.mdx index 09991ecb1..9b184f53b 100644 --- a/content/developer-tools/building-with-llms.mdx +++ b/content/developer-tools/building-with-llms.mdx @@ -18,7 +18,13 @@ We ship an MCP server that exposes the primitives of Knock to LLMs and AI agents You can use the Knock MCP server to aid in building your Knock integration, and to also integrate Knock into any MCP client compatible agent applications. -Learn more in our [MCP Server](/developer-tools/mcp-server) docs. +Learn more in our [MCP Server](/ai/mcp-server) docs. + +## Skills + +Skills are packaged instructions and rules that extend AI agent capabilities with Knock-specific knowledge. Install a skill once and your agent automatically applies the right patterns when working on Knock-related tasks. + +Learn more in our [skills](/ai/skills) docs. ## Knock Agent Toolkit SDK diff --git a/content/developer-tools/integrating-into-cicd.mdx b/content/developer-tools/integrating-into-cicd.mdx index ebec48ce2..a21b7f225 100644 --- a/content/developer-tools/integrating-into-cicd.mdx +++ b/content/developer-tools/integrating-into-cicd.mdx @@ -32,6 +32,8 @@ You can also work with specific resources by providing a `key` rather than using Once you’re ready to send your updates back to Knock’s Development environment, you can push ([`knock push`](/cli/push)) and commit ([`knock commit`](/cli/commit/all)) changes directly from the command line in the same way that you would save and commit them in your dashboard. You can also perform both of these actions at once by using the `--commit` flag on any `push` command. +As an alternative to the push, commit, and promote workflow, you can use the `--force` flag on push commands to bypass environment restrictions and push directly to non-development environments (for example, staging or production). When using `--force` in this way, you should also pass `--commit` so that your changes are persisted. See the [push command reference](/cli/resources/push) for details. + - Note: the Knock MCP server is currently in beta. Due to - the non-deterministic nature of LLMs it's recommended that you test - running the MCP against resources in your development environment before - running it in production. - - } -/> - -Knock ships an MCP server that exposes the primitives of Knock to LLMs and AI via the Model Context Protocol (MCP) so that your AI agents can discover and use Knock via tool calling. - -Here are some examples of how you can use the MCP server in your workflow: - -- **Create workflows using natural language.** "Create a welcome email workflow for my B2B SaaS app." -- **Trigger a specific workflow to test your integration.** "Trigger the comment-created workflow for Dennis Nedry." -- **Create a set of test user and tenant data in your account.** "Create a user called Dennis Nedry and a tenant called acme-corp." - -## Get started - -To get started with the MCP server, you'll need to create a [service token](/developer-tools/service-tokens) to use to authenticate against your Knock account. You can do so on the **Service tokens** page under the **Account** section of your account settings in the Knock dashboard. - -Once you have your service, you can then setup the MCP Server in any MCP Client-compatible AI application. We've added the setup instructions below for both Cursor and Claude Desktop, but the same instructions apply to any other MCP Client-compatible application. - - - Note: To run the local MCP server you must have Node 20 - or higher installed and accessible globally on your system. You can test - this by running `node --version` in your terminal. You can download Node - from{" "} - - the official site - - . - - } -/> -### Cursor - -Click this button to add the Knock MCP server to Cursor (version 1.0 or higher is required). - -
- [![Install MCP - Server](/images/mcp-server/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=knock&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBrbm9ja2xhYnMvYWdlbnQtdG9vbGtpdCIsIi1wIiwibG9jYWwtbWNwIiwiLS1zZXJ2aWNlLXRva2VuIiwiWU9VUi1TRVJWSUNFLVRPS0VOIl0sIm5hbWUiOiJLbm9jayBNQ1AgU2VydmVyIn0=>) -
- -#### Manual installation - -1. Go to **Settings** > **Cursor Settings** and find the "Tools & Integrations" section. -2. Click "New MCP server" under **MCP Tools**. -3. Inside of your `mcp.json` file under the `mcpServers` key, add the following: - -```json -{ - "knock": { - "command": "npx", - "args": ["-y", "@knocklabs/agent-toolkit", "-p", "local-mcp"], - "name": "Knock MCP Server", - "env": { - "KNOCK_SERVICE_TOKEN": "YOUR-SERVICE-TOKEN" - } - } -} -``` - -### Claude Desktop - -1. Open the Claude Desktop settings and find the "Developer" section. -2. Click to "Edit Config." -3. Open your `claude_desktop_config.json` file in your preferred text editor. -4. Add the following contents to the file (or add to the `mcpServers` section if it exists): - -```json -{ - "mcpServers": { - "knock": { - "command": "npx", - "args": ["-y", "@knocklabs/agent-toolkit", "-p", "local-mcp"], - "env": { - "KNOCK_SERVICE_TOKEN": "YOUR-SERVICE-TOKEN" - } - } - } -} -``` - -## Configuring the available tools - -It's not recommended to expose all tools that the Knock MCP server has to the LLM. Instead, you should expose only the tools that are required to complete the task at hand. You can configure the available tools by passing in the `--tools` flag when starting the MCP server. - -The `--tools` flag accepts a list of tools, where the tools are referenced in a `category.tool` format. For example, to expose all user tools, you would pass `--tools users.*`. To expose all workflow tools, you would pass `--tools workflows.*`. - -The name of the tool is the name of the tool as it appears in the [tools reference](/developer-tools/agent-toolkit/tools-reference), which will be a camel-cased version like `createOrUpdateUser` or `listEnvironments`. - -Here are some examples of how you can configure the available tools: - -- Expose all tools: `--tools *` -- Expose all `users` tools: `--tools users.*` -- Expose only the `createOrUpdateUser` tool: `--tools users.createOrUpdateUser` -- Expose all `users` and `workflows` tools: `--tools users.* workflows.*` - -Here's a full example of how you pass tool configurations to the MCP server in Cursor: - -```json title="mcp-with-tools.json" -{ - "knock": { - "command": "npx", - "args": [ - "-y", - "@knocklabs/agent-toolkit", - "-p", - "local-mcp", - "--tools", - "users.createOrUpdateUser", - "workflows.*" - ], - "name": "Knock MCP Server", - "env": { - "KNOCK_SERVICE_TOKEN": "YOUR-SERVICE-TOKEN" - } - } -} -``` - -## Workflows-as-tools - -The Knock MCP server also supports exposing your workflows as individual tools. This allows you to provide a specific and precise interface to the LLM for invoking workflow triggers, including describing the data trigger requirements for your workflows. - -By default, the MCP server will **not** expose any workflows-as-tools. To opt into this behavior, you can pass in the `--workflows` flag when starting the MCP server. - -By default, passing the `--workflows` flag will expose **all** workflows in your Knock account as tools. If you want to control the exact workflows that are exposed as tools, you can pass a list of workflow keys to the `--workflows` flag. For example: - -```bash title="Passing a list of workflow keys" ---workflows workflow-key-1 workflow-key-2 -``` - -## What tools are available? - -The MCP server ships with tools to interact with all Knock resources. You can find the full list of available tools in the [tools reference](/developer-tools/agent-toolkit/tools-reference) of the Knock Agent Toolkit, which the MCP server is built on top of. - -Please note that at this time, the MCP server **does not** ship with any tools to delete resources. This is intentional to prevent the accidental deletion of resources in your Knock account. - -### Workflow-specific tools - -The Knock MCP server exposes a full suite of tools for creating and managing workflows. Using the MCP server you can: - -- Create a workflow with natural language: "create a workflow that sends a welcome email to new users" -- Create a delay or batch step within your workflow: "delay for 3 days" or "batch for 10 minutes" -- Create an email step within your workflow: "create a credit card expiring email with a link back to the dashboard" -- Create an SMS, push, or in-app feed step within your workflow - -Using these tools you can create a complex prompt that describes one or more workflows that you'd like to create with natural language. - -## Related links - -- [Building with LLMs](/developer-tools/building-with-llms) -- [Knock Agent Toolkit](/developer-tools/agent-toolkit/overview) - -## Frequently asked questions - - - - No, currently we support a local MCP server only. We are working - on a remote MCP server so that an individual Knock account can be - authenticated. - - - Some MCP clients will warn you about having more than 50 tools. By default, Knock ships with 30 tools, but when using workflows-as-tools, this number can grow quickly. - - To address this, you should opt into only using the tools you need. For - example, if you only use the `users.create` tool, you can expose only that - tool by passing in the `--tools` flag. - - - - The local MCP server requires Node 20 or higher and Claude may be defaulting to an older Node version if you have multiple versions installed. Try uninstalling older versions of Node and then restarting Claude to resolve the issue. - - - If you see an error like _"The model returned an error. Try disabling MCP servers, or switch models,"_ check which model is selected for the Cursor agent. Make sure it's explicitly set to a supported model like `claude-4-sonnet` rather than relying on automatic model selection. - - diff --git a/content/developer-tools/migration-guides/go.mdx b/content/developer-tools/migration-guides/go.mdx index 7fb377e22..723936bb8 100644 --- a/content/developer-tools/migration-guides/go.mdx +++ b/content/developer-tools/migration-guides/go.mdx @@ -261,4 +261,4 @@ object, err := client.Objects.Set( ## Need help? -If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on [GitHub](https://github.com/knocklabs/knock-go/issues). +If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. diff --git a/content/developer-tools/migration-guides/java.mdx b/content/developer-tools/migration-guides/java.mdx index 9c2f30e5f..236b2cff6 100644 --- a/content/developer-tools/migration-guides/java.mdx +++ b/content/developer-tools/migration-guides/java.mdx @@ -372,4 +372,4 @@ client.users().unsetChannelData( ## Need help? -If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on [GitHub](https://github.com/knocklabs/knock-java/issues). +If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. diff --git a/content/developer-tools/migration-guides/node.mdx b/content/developer-tools/migration-guides/node.mdx index 472c5b6f3..04f73d809 100644 --- a/content/developer-tools/migration-guides/node.mdx +++ b/content/developer-tools/migration-guides/node.mdx @@ -232,4 +232,4 @@ await client.users.update("jhammond", { ## Need help? -If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on [GitHub](https://github.com/knocklabs/knock-node/issues). +If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. diff --git a/content/developer-tools/migration-guides/python.mdx b/content/developer-tools/migration-guides/python.mdx index 26220b1c6..09b9df98d 100644 --- a/content/developer-tools/migration-guides/python.mdx +++ b/content/developer-tools/migration-guides/python.mdx @@ -287,4 +287,4 @@ client.tenants.set( ## Need help? -If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on [GitHub](https://github.com/knocklabs/knock-python/issues). +If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. diff --git a/content/developer-tools/migration-guides/ruby.mdx b/content/developer-tools/migration-guides/ruby.mdx index 987135183..6afa4b70c 100644 --- a/content/developer-tools/migration-guides/ruby.mdx +++ b/content/developer-tools/migration-guides/ruby.mdx @@ -212,4 +212,4 @@ The new SDK returns model instances rather than raw hashes. Pagination is handle ## Need help? -If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on [GitHub](https://github.com/knocklabs/knock-ruby/issues). +If you run into any issues during your migration, reach out to our [support team](mailto:support@knock.app) or open an issue on GitHub. diff --git a/content/developer-tools/outbound-webhooks/event-types.mdx b/content/developer-tools/outbound-webhooks/event-types.mdx index 4869941ed..102d50aa3 100644 --- a/content/developer-tools/outbound-webhooks/event-types.mdx +++ b/content/developer-tools/outbound-webhooks/event-types.mdx @@ -7,6 +7,17 @@ tags: ["events", "data", "analytics", "webhook configuration"] ## Message events + + The event_data field is event-specific and will be{" "} + null when not applicable. When present, its available fields + and content may vary by channel provider. + + } +/> + ### `message.sent` Occurs when a message is successfully sent to a channel provider. @@ -48,27 +59,27 @@ Occurs when a message delivery attempt fails and may be retried. name="event_data" type="EventData" description="Additional information about the attempt, the total attempts, and whether or not it will be retried." - /> - - -`EventData` attributes: - - - - - + expandLabel="EventData attributes" + > + + + + + + ### `message.undelivered` @@ -86,22 +97,21 @@ Occurs when a message delivery attempt fails permanently. Delivery will not be r name="event_data" type="EventData" description="May include additional information about the failure." - /> - - -`EventData` attributes: - - - - + expandLabel="EventData attributes" + > + + + + + ### `message.bounced` @@ -119,27 +129,26 @@ Occurs when a message delivery attempt fails due to bounce or a delivery status name="event_data" type="EventData" description="May include additional information about the failure." - /> - - -`EventData` attributes: - - - - - + expandLabel="EventData attributes" + > + + + + + + ### `message.seen` @@ -233,7 +242,7 @@ Occurs when a message is interacted with by its recipient. For the Knock in-app /> @@ -253,17 +262,16 @@ Occurs when a link is clicked by the message recipient. This is only available w name="event_data" type="EventData" description="May include additional information about the link click event." - /> - - -`EventData` attributes: - - - + expandLabel="EventData attributes" + > + + + + ## Workflow events @@ -296,17 +304,16 @@ Occurs whenever a workflow is committed to the environment. name="event_data" type="EventData" description="Additional information about the commit." - /> - - -`EventData` attributes: - - - + expandLabel="EventData attributes" + > + + + + ## Email layout events @@ -339,17 +346,16 @@ Occurs whenever an email layout is committed to the environment. name="event_data" type="EventData" description="Additional information about the commit." - /> - - -`EventData` attributes: - - - + expandLabel="EventData attributes" + > + + + + ## Translation events @@ -382,17 +388,16 @@ Occurs whenever a translation is committed to the environment. name="event_data" type="EventData" description="Additional information about the commit." - /> - - -`EventData` attributes: - - - + expandLabel="EventData attributes" + > + + + + ## Source event action events @@ -425,17 +430,16 @@ Occurs whenever a source event action is committed to the environment. name="event_data" type="EventData" description="Additional information about the commit." - /> - - -`EventData` attributes: - - - + expandLabel="EventData attributes" + > + + + + ## Partial events @@ -468,15 +472,14 @@ Occurs whenever a partial is committed to the environment. name="event_data" type="EventData" description="Additional information about the commit." - /> - - -`EventData` attributes: - - - + expandLabel="EventData attributes" + > + + + + diff --git a/content/developer-tools/outbound-webhooks/overview.mdx b/content/developer-tools/outbound-webhooks/overview.mdx index 611648935..cea6354fa 100644 --- a/content/developer-tools/outbound-webhooks/overview.mdx +++ b/content/developer-tools/outbound-webhooks/overview.mdx @@ -23,19 +23,22 @@ You can learn more about the types of events that [Knock sends webhooks for here A webhook payload will always take the following base shape, where the `type` and its associated `data` schema will be determined by the supported [webhook event types](/developer-tools/outbound-webhooks/event-types). The `data` field will include the entity that the event references. +Some event types also include an `event_data` field with additional context specific to that event β€” for example, failure details on a `message.undelivered` event or the clicked URL on a `message.link_clicked` event. For event types that do not include additional context, `event_data` will be `null`. See the [webhook event types](/developer-tools/outbound-webhooks/event-types) page for a full breakdown of which events include `event_data` and what fields each contains. + ```json title="A sample event payload" { "__typename": "Event", // The type of event that triggered the webhook - "type": "message.sent", - "created_at": "2022-05-31T17:12:59.958652Z", + "type": "message.undelivered", + "created_at": "2026-01-31T17:12:59.958652Z", "data": { // Information about the entity, as determined by the webhook event type. }, - // (Optional) additional metadata related to the event + // Only present for event types that include additional metadata. Null otherwise. "event_data": { "__typename": "EventData", - "foo": "bar" + "failure_reason": "fatal_error", + "failure_details": "The message could not be delivered to the provider." } } ``` @@ -44,7 +47,7 @@ The request header will also contain the following: - `x-knock-event`: the field and the value found in the `"type"` key of the payload - `x-knock-environment-id`: the environment the webhook belongs to -- `x-knock-signature`: the encoded result of a timestamp, payload, and shared secret key so you can verify that the contents of the webhook are from Knock and not a result of a [Man in the middle attack](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). Learn more about [verifying signatures below](/developer-tools/outbound-webhooks/overview#verifying-the-signature). +- `x-knock-signature`: the encoded result of a timestamp, payload, and shared secret key so you can verify that the contents of the webhook are from Knock and not a result of a Man in the middle attack. Learn more about [verifying signatures below](/developer-tools/outbound-webhooks/overview#verifying-the-signature). privacy policy. ## Our security posture -Knock is SOC 2 Type 2 compliant, GDPR certified, CCPA, and HIPAA compliant. We perform regular penetration tests. If you'd like copies of our SOC 2 report or penetration test report, please let us know at [security@knock.app](mailto:security@knock.app). You can learn more about our GDPR certification in our [privacy policy](https://knock.app/legal/privacy-policy). +Knock is SOC 2 Type 2 compliant, GDPR certified, CCPA, and HIPAA compliant. We perform regular penetration tests. If you'd like copies of our SOC 2 report or penetration test report, please let us know at [security@knock.app](mailto:security@knock.app). You can learn more about our GDPR certification in our privacy policy.
@@ -20,6 +20,7 @@ Knock is SOC 2 Type 2 compliant, GDPR certified, CCPA, and HIPAA compliant. We p alt="SOC2 Service Organization Logo" width={120} height={120} + border={false} />
@@ -28,6 +29,7 @@ Knock is SOC 2 Type 2 compliant, GDPR certified, CCPA, and HIPAA compliant. We p alt="GDPR badge" width={120} height={120} + border={false} className="self-center ml-2" />
@@ -37,6 +39,7 @@ Knock is SOC 2 Type 2 compliant, GDPR certified, CCPA, and HIPAA compliant. We p alt="HIPAA badge" width={120} height={120} + border={false} className="self-center" />
diff --git a/content/developer-tools/type-safety.mdx b/content/developer-tools/type-safety.mdx index e9bbf153b..0b0faade9 100644 --- a/content/developer-tools/type-safety.mdx +++ b/content/developer-tools/type-safety.mdx @@ -8,7 +8,7 @@ Using Knock you can get type safety between your codebase and the workflows that ## Setting up a workflow trigger schema -Before you can generate types for your workflow triggers, you need to define a [JSON schema](https://json-schema.org/) that describes the expected structure of the data passed to your workflow, which we call a [trigger data schema](/designing-workflows/validating-trigger-data). This schema serves as the source of truth for type generation. +Before you can generate types for your workflow triggers, you need to define a JSON schema that describes the expected structure of the data passed to your workflow, which we call a [trigger data schema](/designing-workflows/validating-trigger-data). This schema serves as the source of truth for type generation. You can set up trigger data validation in two ways: diff --git a/content/getting-started/quick-start.mdx b/content/getting-started/quick-start.mdx index b3fd151e6..25a91e94c 100644 --- a/content/getting-started/quick-start.mdx +++ b/content/getting-started/quick-start.mdx @@ -9,7 +9,7 @@ This page will help you integrate Knock with your backend web application and se - First, [create a Knock account](https://dashboard.knock.app/signup) if you don't already have one and log into the [Knock dashboard](https://dashboard.knock.app). + First, create a Knock account if you don't already have one and log into the Knock dashboard. We have SDKs available in [most major languages](/developer-tools/sdks). Don't see your language listed here? [Let us know](mailto:support@knock.app)! diff --git a/content/getting-started/quick-start/general.mdx b/content/getting-started/quick-start/general.mdx index b6c592681..7a17a7224 100644 --- a/content/getting-started/quick-start/general.mdx +++ b/content/getting-started/quick-start/general.mdx @@ -9,7 +9,7 @@ This page covers how to integrate Knock with your backend web application and se - First, [create a Knock account](https://dashboard.knock.app/signup) if you don't already have one and log into the [Knock dashboard](https://dashboard.knock.app). + First, create a Knock account if you don't already have one and log into the Knock dashboard. We have SDKs available in [most major languages](/developer-tools/sdks). Don't see your language listed here? [Let us know](mailto:support@knock.app)! diff --git a/content/getting-started/quick-start/nextjs.mdx b/content/getting-started/quick-start/nextjs.mdx index a9bb5e257..8d33a7061 100644 --- a/content/getting-started/quick-start/nextjs.mdx +++ b/content/getting-started/quick-start/nextjs.mdx @@ -9,10 +9,10 @@ This page covers how to integrate Knock's `NotificationFeed` component in Next.j - First, [create a Knock account](https://dashboard.knock.app/signup) if you don't already have one and log into the [Knock dashboard](https://dashboard.knock.app). + First, create a Knock account if you don't already have one and log into the Knock dashboard. - Run the following command to [create a new Next.js application](https://nextjs.org/docs/app/getting-started/installation). + Run the following command to create a new Next.js application. ```bash title="Command to create a new Next.js app" npm create next-app@latest knock-next-quickstart diff --git a/content/getting-started/quick-start/react.mdx b/content/getting-started/quick-start/react.mdx index 081f0cc3b..79d302fe0 100644 --- a/content/getting-started/quick-start/react.mdx +++ b/content/getting-started/quick-start/react.mdx @@ -9,10 +9,10 @@ This page covers how to integrate Knock's `NotificationFeed` component in a Reac - First, [create a Knock account](https://dashboard.knock.app/signup) if you don't already have one and log into the [Knock dashboard](https://dashboard.knock.app). + First, create a Knock account if you don't already have one and log into the Knock dashboard. - Run the following command to [create a new React application](https://react.dev/learn/build-a-react-app-from-scratch#vite). + Run the following command to create a new React application. ```bash title="Command to create a new React app" npm create vite@latest knock-react-quickstart -- --template react-ts diff --git a/content/getting-started/what-is-knock.mdx b/content/getting-started/what-is-knock.mdx index cd23c23c3..982efb4ef 100644 --- a/content/getting-started/what-is-knock.mdx +++ b/content/getting-started/what-is-knock.mdx @@ -394,6 +394,6 @@ There is a lot more to learn about Knock, and our [concepts overview page](/conc Knock is a developer-first platform, with both [environment](/concepts/environments) and [commit models](/concepts/commits). If you want to work with Knock resources in code, you can use our [Management API](/developer-tools/management-api) or [CLI](/developer-tools/knock-cli). -Knock also supports an [MCP](/developer-tools/mcp-server) server, which allows you to work with Knock resources in your IDE, entirely using natural language. Our MCP server is a great way to migrate from a legacy system to Knock, or to make updates across many Knock resources at once. +Knock also supports an [MCP](/ai/mcp-server) server, which allows you to work with Knock resources in your IDE, entirely using natural language. Our MCP server is a great way to migrate from a legacy system to Knock, or to make updates across many Knock resources at once. Once you're sending messages through Knock, we offer observability tools like [workflow run logs](/send-notifications/debugging-workflows) (to examine all steps of workflow execution in your dashboard) and data streaming into a monitoring system like Datadog with [extensions](/integrations/extensions/overview). diff --git a/content/in-app-ui/android/sdk/overview.mdx b/content/in-app-ui/android/sdk/overview.mdx index 9968f94c4..40850d065 100644 --- a/content/in-app-ui/android/sdk/overview.mdx +++ b/content/in-app-ui/android/sdk/overview.mdx @@ -13,7 +13,7 @@ The Knock Kotlin SDK is a client-side SDK for interacting with the Knock API and ## Example app -You can find a complete Android example application that uses the Knock Android SDK [here](https://github.com/knocklabs/knock-android). The app shows patterns for handling push token registration, building an in-app feed using Combine, and managing user notification preferences. +You can find a complete Android example application that uses the Knock Android SDK here. The app shows patterns for handling push token registration, building an in-app feed using Combine, and managing user notification preferences. ## Need help? @@ -30,4 +30,4 @@ Our Android SDK is worked on full-time by the Knock Mobile team. ### Contributing -All contributors are welcome, from casual to regular. Feel free to open a [pull request](https://github.com/knocklabs/knock-android/pulls/new). +All contributors are welcome, from casual to regular. Feel free to open a pull request. diff --git a/content/in-app-ui/android/sdk/push-notifications.mdx b/content/in-app-ui/android/sdk/push-notifications.mdx index 2148915a7..b43236ca6 100644 --- a/content/in-app-ui/android/sdk/push-notifications.mdx +++ b/content/in-app-ui/android/sdk/push-notifications.mdx @@ -4,16 +4,16 @@ description: "Documentation to help you get started with the Push Notifications section: SDKs --- -**Note:** We Recommend taking advantage of our [KnockMessagingService & KnockActivity](/in-app-ui/android/sdk/reference#knockmessagingservice--knockactivity) to make managing your Push Notifications simpler. +**Note:** We recommend taking advantage of our [KnockMessagingService & KnockActivity](/in-app-ui/android/sdk/reference#knockmessagingservice--knockactivity) to make managing your push notifications simpler. ## Prerequisites - Before proceeding, ensure you've configured push notifications within your Knock account. For guidance on this initial setup, refer to our [push notification documentation](/integrations/push/overview). -- Review Firebase's [documentation](https://firebase.google.com/docs/cloud-messaging) for advanced features and updated practices. +- Review Firebase's documentation for advanced features and updated practices. ## 1. Create a Firebase project -- **Go to the [Firebase Console](https://console.firebase.google.com/).** +- **Go to the Firebase Console.** - **Click on "Add project"** and follow the on-screen instructions to create a new Firebase project. ## 2. Add your Android app to the Firebase project @@ -64,7 +64,7 @@ section: SDKs ``` -## 5: Requesting user permission for push notifications +## 5. Requesting user permission for push notifications - To prompt the user to approve or deny push notification permissions call the `Knock.shared.requestNotificationPermission()` method. @@ -96,7 +96,7 @@ section: SDKs } ``` -## 5. Receive push notifications +## 7. Receive push notifications To detect when a push notification is received in the foreground: diff --git a/content/in-app-ui/android/sdk/quick-start.mdx b/content/in-app-ui/android/sdk/quick-start.mdx index 903f9a543..2d6d06d4f 100644 --- a/content/in-app-ui/android/sdk/quick-start.mdx +++ b/content/in-app-ui/android/sdk/quick-start.mdx @@ -18,7 +18,7 @@ You can install the Android SDK in a the following ways: - Jitpack - Manually -See [here](https://github.com/knocklabs/knock-android) for more information on installation. +See here for more information on installation. ### Initializing a Knock instance diff --git a/content/in-app-ui/angular/overview.mdx b/content/in-app-ui/angular/overview.mdx index 5085f9fb0..d894ed3c7 100644 --- a/content/in-app-ui/angular/overview.mdx +++ b/content/in-app-ui/angular/overview.mdx @@ -6,6 +6,6 @@ section: Building in-app UI Today we do not have Knock-built, out-of-the-box Angular components for in-app notifications UI. -Our Knock community built this [Angular in-app feed example](https://github.com/knocklabs/angular-in-app-feed-example) you can use to get started with an Angular in-app feed in your own application. +Our Knock community built this Angular in-app feed example you can use to get started with an Angular in-app feed in your own application. -If you have any questions, or if you have an in-app use case that is blocking your adoption of Knock, please [contact our sales team](https://knock.app/contact-sales). +If you have any questions, or if you have an in-app use case that is blocking your adoption of Knock, please contact our sales team. diff --git a/content/in-app-ui/api-overview.mdx b/content/in-app-ui/api-overview.mdx index 0d490c582..428029475 100644 --- a/content/in-app-ui/api-overview.mdx +++ b/content/in-app-ui/api-overview.mdx @@ -85,7 +85,7 @@ Please note, by default all `archived` messages are not displayed. ### `FeedItem` -Requests to the feed endpoint will return an array of `FeedItems`. Each feed item includes: +Requests to the feed endpoint return an array of `FeedItem` objects. The shape of each item depends on the `mode` set when initializing the feed β€” the default is `compact`. For more information on setting the feed's mode, see [initialize the feed instance](/in-app-ui/react/headless/feed#initialize-the-feed-instance). - Expo SDK example app shows patterns for handling push token registration, building an in-app feed, and managing user notification preferences. ## Need help? @@ -157,4 +157,4 @@ Ask questions and find answers on the following platforms: ### Contributing -All contributors are welcome, from casual to regular. Feel free to open a [pull request](https://github.com/knocklabs/javascript/pulls/new). +All contributors are welcome, from casual to regular. Feel free to open a pull request. diff --git a/content/in-app-ui/feeds/custom-ui.mdx b/content/in-app-ui/feeds/custom-ui.mdx index c98954c69..5b5481aba 100644 --- a/content/in-app-ui/feeds/custom-ui.mdx +++ b/content/in-app-ui/feeds/custom-ui.mdx @@ -23,7 +23,13 @@ If you're using the React Native SDK, you can use our hooks to build your own fe ## Flutter -If you're using the Flutter SDK, you can use the FeedClient instance to build your own feed UI on top of. You'll find more in the [Flutter SDK documentation](/in-app-ui/flutter/sdk/reference). +If you're using the Flutter SDK, use [`FeedClient`](/in-app-ui/flutter/sdk/reference#feedclient) to drive your feed UI. When a screen or scope goes away, call [`feedClient.dispose()`](/in-app-ui/flutter/sdk/reference#dispose-1) so sockets and streams tear down. + +As of [`knock_flutter` 1.0.0](https://pub.dev/packages/knock_flutter), public API types use a `Knock` prefix (for example `KnockApiClient`, `KnockApiResponse`, and `KnockApiException` instead of `ApiClient`, `ApiResponse`, and `ApiError`). `KnockApiException` implements [`Exception`](https://api.dart.dev/dart-core/Exception-class.html), not `Error`, so update any `catch` clauses that assumed `ApiError` extended `Error`. + +The SDK does not collect FCM or APNs tokens. Add something like [`firebase_messaging`](https://pub.dev/packages/firebase_messaging) in your app, read the device token yourself, then pass it to [`UserClient.registerTokenForChannel`](/in-app-ui/flutter/sdk/reference#registertokenforchannel). The SDK [README migration table](https://github.com/knocklabs/knock-flutter/blob/main/README.md#migrating-from-01x-to-100) and [example README](https://github.com/knocklabs/knock-flutter/blob/main/example/README.md) cover renames and Firebase-oriented setup. + +You’ll find the full surface area in the [Flutter SDK reference](/in-app-ui/flutter/sdk/reference). ## iOS (Swift) diff --git a/content/in-app-ui/flutter/sdk/overview.mdx b/content/in-app-ui/flutter/sdk/overview.mdx index 94784a2ce..9eb4041a8 100644 --- a/content/in-app-ui/flutter/sdk/overview.mdx +++ b/content/in-app-ui/flutter/sdk/overview.mdx @@ -5,7 +5,27 @@ section: SDKs tags: ["flutter", "dart"] --- -Our [`knock_flutter`](https://pub.dev/packages/knock_flutter) library lets you create in-app notification experiences in Flutter applications using Knock's client APIs. +Our `knock_flutter` library lets you create in-app notification experiences in Flutter applications using Knock's client APIs. + + + + Version 1.0.0 + {" "} + is a breaking release. Highlights include a pure-Dart package (no native + plugin), API types renamed with a Knock prefix, and{" "} + KnockApiException now implementing Exception{" "} + (not Error). + + } +/> **Quick links:** @@ -16,7 +36,7 @@ Our [`knock_flutter`](https://pub.dev/packages/knock_flutter) library lets you c Using the Flutter SDK it's possible to: -- Integrate push notifications (manual engagement status handling required) +- **Register devices for push.** Token retrieval is your responsibility (for example with firebase_messaging); the SDK forwards tokens to Knock channels via UserClient.registerTokenForChannel. See the SDK README section on push notifications (manual engagement handling still applies where relevant). - Build in-app experiences, like feeds that update in real-time - Create notification preference control centers @@ -36,4 +56,4 @@ Ask questions and find answers on the following platforms: ### Contributing -All contributors are welcome, from casual to regular. Feel free to open a [pull request](https://github.com/knocklabs/knock-flutter/pulls/new). +All contributors are welcome, from casual to regular. Feel free to open a pull request. diff --git a/content/in-app-ui/flutter/sdk/quick-start.mdx b/content/in-app-ui/flutter/sdk/quick-start.mdx index 5abd2f291..00d1db9c6 100644 --- a/content/in-app-ui/flutter/sdk/quick-start.mdx +++ b/content/in-app-ui/flutter/sdk/quick-start.mdx @@ -12,19 +12,80 @@ The Knock Flutter SDK is a client-side SDK for interacting with the Knock API an - [Full reference documentation](/in-app-ui/flutter/sdk/reference) - [Example application](https://github.com/knocklabs/knock-flutter/tree/main/example) +## Requirements + +- Dart SDK `>=3.8.0` +- Flutter `>=3.32.0` + +(These match the `environment` constraints in the package pubspec.yaml.) + ## Installation -You can find `knock_flutter` on [pub.dev](https://pub.dev/packages/knock_flutter). +You can find `knock_flutter` on pub.dev. ```bash flutter pub add knock_flutter ``` +That command resolves to the current 1.x line. If you are upgrading from `0.1.x`, use the migration table in the SDK README. + ## Setup the SDK ```dart import 'package:knock_flutter/knock_flutter.dart'; -late Knock knock = Knock(const String.fromEnvironment("KNOCK_API_KEY")); -knock.authenticate("your-user-id"); +final knock = Knock( + const String.fromEnvironment('KNOCK_API_KEY'), + // Optional: override the API host (e.g. for staging). + // options: KnockOptions(host: 'https://api.knock.app'), +); + +knock.authenticate('your-user-id', 'optional-signed-user-token'); + +// When you are done with the Knock instance (e.g. logout / app teardown): +knock.dispose(); ``` + + + Earlier versions shipped a native plugin for token helpers; 1.0.0 does + not. Add push dependencies yourself (for example{" "} + + firebase_messaging + + ), obtain the device token in your app, then register it with + registerTokenForChannel + . See the README + push notifications + section for patterns and caveats. + + } +/> + + + Each knock.feed(...) call returns a new{" "} + FeedClient. When you no longer need it (for example when a + screen is disposed), call feedClient.dispose() to release + sockets and streams. See the{" "} + + FeedClient lifecycle + {" "} + notes on the reference page. + + } +/> diff --git a/content/in-app-ui/flutter/sdk/reference.mdx b/content/in-app-ui/flutter/sdk/reference.mdx index 9120ce0a4..7a8cecf1c 100644 --- a/content/in-app-ui/flutter/sdk/reference.mdx +++ b/content/in-app-ui/flutter/sdk/reference.mdx @@ -33,6 +33,10 @@ The top-level Knock class. Create an authenticated Knock client instance for int Authenticates the current user. +```dart +void authenticate(String userId, [String? userToken]) +``` + **Params** @@ -43,8 +47,8 @@ Authenticates the current user. /> @@ -70,27 +74,31 @@ Returns whether or this Knock instance is authenticated. Passing `true` will che ### `dispose` -Releases any connected resources used by this instance. +Disposes the cached `KnockApiClient`, which closes the Phoenix socket and ends the client status stream. Call this when the Knock instance is no longer needed (for example on logout or app teardown). Call it alongside (or before) `FeedClient.dispose()` for any feed clients you created. **Returns** `void` -### `getApnsToken` +### Push tokens + +The SDK does not read FCM or APNs tokens. Obtain tokens in your app (for example with [`firebase_messaging`](https://pub.dev/packages/firebase_messaging)), then register them with [`registerTokenForChannel`](#registertokenforchannel). See the README section on [push notifications](https://github.com/knocklabs/knock-flutter/blob/main/README.md#push-notifications). + +### `client` -Returns the current device's APNs token. Only relevant for iOS applications. +Returns the shared `KnockApiClient` used for HTTP and Phoenix traffic. Most apps do not need this; it is cached lazily and is useful for advanced integration or debugging. **Returns** -`Future` +`KnockApiClient` -### `getFcmToken` +### `messages` -Returns the current device's FCM token. +Returns the [`MessagesClient`](#messagesclient) for the authenticated user. **Returns** -`Future` +`MessagesClient` ### `user` @@ -133,6 +141,36 @@ Returns a new feed client for the channel ID specified. --- +## Types + +These types appear throughout the public API. In 1.0.0, several were renamed with a `Knock` prefix. + +### `KnockApiClient` + +HTTP client and Phoenix connection manager (formerly `ApiClient`). You usually access it only via [`Knock.client()`](#client) if at all. + +### `KnockApiResponse` + +Wrapper for successful API payloads (formerly `ApiResponse`). + +### `KnockApiClientStatus` + +Status values for the shared API client connection (formerly `ApiClientStatus`). + +### `KnockApiException` + +Thrown when the API returns an error response (formerly `ApiError`). It implements `Exception`, not `Error`, so use `on KnockApiException catch (e)` (not `on Error`). Inspect fields such as `response.status` for HTTP details. + +### `NetworkStatus` + +Enum describing feed load state: `initial`, `loading`, `fetchMore`, `ready`, and `error`. `Feed.initialState()` uses `NetworkStatus.initial`. Prefer checking `feed.requestInFlight` (and related flags) instead of comparing to `NetworkStatus.ready` only. See the [`feed`](#feed-1) stream on `FeedClient`. + +### `KnockOptions` + +Optional configuration for [`Knock`](#knock). Fields include `host` to override the API base URL (for example for staging). + +--- + ## `UserClient` Methods for interacting with the current user resource in Knock. You access this under `knock.user()`. @@ -149,6 +187,8 @@ Returns the current authenticated user from Knock. Upserts the current authenticated user properties in Knock. +Named parameters (`email`, `name`, `phoneNumber`, `avatar`, `locale`) use a sentinel default: **omitting** a parameter leaves that field unchanged in Knock; **passing** `null` clears it. + **Returns** `Future` @@ -196,6 +236,8 @@ Updates the channel data for the current user on the channel specified with `cha Registers the current device's token for the user in Knock. +Failed HTTP responses (4xx/5xx) throw `KnockApiException`. + **Params** @@ -209,6 +251,11 @@ Registers the current device's token for the user in Knock. type="String" description="The device token to register." /> + **Returns** @@ -219,6 +266,8 @@ Registers the current device's token for the user in Knock. De-registers the current device's token for the user in Knock. +Failed HTTP responses (4xx/5xx) throw `KnockApiException`. + **Params** @@ -242,6 +291,22 @@ De-registers the current device's token for the user in Knock. ## `PreferencesClient` +Access via `knock.preferences()` or `knock.preferences(options: ...)`. + +### `PreferencesOptions` + +Constructor options for the preferences client. + +**Params** + + + + + ### `getAll` Returns all preference sets for the current user. @@ -276,6 +341,8 @@ Returns a single preference set for the current user. ## `FeedClient` +Returned from `knock.feed(...)`. Each call returns a **new** client; you own its lifetime. + @@ -476,3 +557,119 @@ Marks all items in the user's feed as archived. Will optimistically update the i **Returns** `Future` + +--- + +## `MessagesClient` + +Returned by [`knock.messages()`](#messages). Reads and updates individual [messages](/concepts/messages) for the authenticated user. API failures throw `KnockApiException`. + +### `get` + +**Params** + + + + + +**Returns** + +`Future` + +### `updateStatus` + +Sets engagement status on a message. The SDK method also accepts optional named request parameters. + +**Params** + + + + + + +**Returns** + +`Future` + +### `removeStatus` + +**Params** + + + + + + +**Returns** + +`Future` + +### `batchUpdateStatuses` + +Applies a batch status change. The SDK method also accepts optional named request parameters. + +**Params** + + + + + + +**Returns** + +`Future>` + +### `bulkUpdateAllStatusesInChannel` + +Runs a bulk status update for messages in a channel. See the SDK signature for the full set of named parameters (including optional request options). + +**Params** + + + + + + +**Returns** + +`Future` + +### Convenience helpers + +`markAsSeen`, `markAsRead`, `markAsArchived`, `markAsInteracted`, `markAsUnseen`, `markAsUnread`, and `markAsUnarchived` each take a `messageId` and delegate to `updateStatus` or `removeStatus`. diff --git a/content/in-app-ui/guides/debugging-guides.mdx b/content/in-app-ui/guides/debugging-guides.mdx index b4443b52b..da1fcd9f1 100644 --- a/content/in-app-ui/guides/debugging-guides.mdx +++ b/content/in-app-ui/guides/debugging-guides.mdx @@ -5,79 +5,123 @@ tags: ["guides", "testing", "debugging", "development", "troubleshooting"] section: Building in-app UI > Guides --- -Testing guides is essential to ensure they work correctly for your users before going live. This page covers how to test guides during development and troubleshoot issues that may arise during testing. Beyond local testing in your development environment, Knock provides Dashboard resources for testing guide edits in realtime, making guides more accessible to non-technical users. +Testing guides is essential to ensure they work correctly for your users before going live. This page covers how to test guides during development and troubleshoot issues that may arise during testing. -## Testing guides +## Dashboard preview -### Live preview +You can preview guides directly in the Knock dashboard using the preview pane, which is powered by your message type's [template preview](/in-app-ui/react/sdk/components/knock-guide-provider): + +1. Navigate to your guide in the dashboard and open the guide editor. +2. Use the preview pane on the right side to see how your guide content will render. +3. Edit your guide content and see the preview update in real-time. +4. Test different variants and content to refine your guide. + +To ensure consistency between the dashboard's template preview and the guide component in your application, keep your message type's template preview code aligned with your component code. We recommend using the guides toolbar to ensure your actual code renders your guide schema as expected. + +## Guides toolbar + + + +The guides toolbar is a floating panel that enables you to inspect and debug guides on any page of your application. Use it to understand why a guide is or isn't showing in a given context, force guides to display for testing, and inspect the targeting parameters being evaluated for the current user. + Guides toolbar requires @knocklabs/react v0.11.13 or higher. + + } /> -Live preview enables you to preview guides directly within your own application from the Knock dashboard, enabling realtime previews for non-technical users and expediting the guide creation process. +### Activating the toolbar -With live preview, you can: +1. In the Knock dashboard, click the **Preview** option above your list of guides, or at the top of the guide editor's preview pane. +2. Provide or confirm the URL for your environment and click **Open preview**. The URL should point to a page where the guide's target element exists and the [guide provider is initialized](/in-app-ui/guides/render-guides). +3. After clicking **Open preview**, the toolbar appears as a floating panel that you can expand or collapse. -- Preview how your guides will look and behave within your actual application. -- Edit guide content while seeing exactly how they'll appear to users. -- Get real-time validation and immediate feedback when a guide cannot render on the current page. -- Validate that prebuilt components work correctly with your message type schema. -- Iterate on guide designs without activating test guides in your dashboard. +You can also reopen the toolbar at any time by appending `?knock_guides_toolbar=true` to any URL in your application. -Note that in live preview mode, the guide component is force rendered to show how it looks within your application, so targeting rules and other eligibility conditions that would normally prevent the guide from rendering are ignored. +### Toolbar views - +The toolbar provides three views, toggled from options at the top of the panel: -#### Getting started with live preview +- **All.** Every guide in your environment. +- **Active.** All activated guides in the environment. +- **Eligible.** All guides that are eligible for the current user. -1. Before creating your guide, [implement guides in your application](/in-app-ui/guides/render-guides) and ensure the relevant guide component is available on the target page in your application. -2. Navigate to your guide in the Knock dashboard and open the guide editor. -3. Click "Live preview" at the top of the guide editor. -4. Enter the URL of the page in your application where you will preview the guide. -5. Click "Open preview" to see your guide rendered in your application. +### Status badges -Live preview integrates seamlessly with your existing [`KnockGuideProvider`](/in-app-ui/guides/render-guides) setup and doesn't require additional code changes to your application. +Each guide row shows a status badge indicating its current state: - +| Status | Description | +| ----------------- | --------------------------------------------------------------- | +| **Inactive** | The guide has not been published or is currently inactive. | +| **Archived** | The current user has already dismissed this guide. | +| **Not targeted** | The current user doesn't meet the guide's targeting conditions. | +| **Not queried** | No component that can render this guide was found on the page. | +| **Not activated** | The guide cannot be activated in the current location. | +| **Queued** | The guide is queried but is not yet ready to display. | +| **Throttled** | The guide is ready to display but is currently throttled. | +| **Display** | The guide is available and ready to display. | -### Dashboard preview +### Eligibility indicators -You can preview guides directly in the Knock dashboard using the preview pane, which is powered by your message type's [template preview](/in-app-ui/react/sdk/components/knock-guide-provider): +Each guide row displays three eligibility ("Elig.") indicators. A filled dot means the condition passes; an X means it fails. All three must pass for a guide to display. -1. Navigate to your guide in the dashboard and open the guide editor. -2. Use the preview pane on the right side to see how your guide content will render. -3. Edit your guide content and see the preview update in real-time. -4. Test different variants and content to refine your guide. +1. **Active.** The guide is published and active. +2. **Archived.** The guide has been dismissed for the current user. +3. **Targeted.** The current user matches the guide's [targeting conditions](/in-app-ui/guides/create-guides#targeting). - -### Testing best practices +Select the settings icon to expand the toolbar settings panel. From there, you can control the behavior of engagement tracking while testing, toggle throttle settings, and view the data currently passed to the guide provider. The following controls are available: + +| Control | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Sandbox engagement** | When enabled, engagement API calls (seen, interacted, dismissed) are not sent to Knock. Local state still updates so the UI reflects changes, but nothing is recorded in Knock. Use this to test continuously without needing to reset engagement. | +| **Ignore throttle** | When enabled, the display interval throttle window is bypassed so guides can display immediately, regardless of when the last guide was dismissed. | +| **Target params** | This section displays the active tenant ID and any additional data provided to the guide provider. Depending on guide configuration, this data may be used for eligibility evaluation. | + +## Testing best practices Before launching your guides to users, test them thoroughly to ensure they work as expected. Focus on these key areas: -- **Page targeting**. Verify that guides appear on the correct pages by testing different URLs and path patterns in your activation rules. -- **User targeting**. Confirm that targeting conditions work correctly by testing with users who should and shouldn't see the guide based on your eligibility criteria. -- **Engagement tracking events**. Interact with your guides and verify that engagement status updates appear as expected in the dashboard (seen, interacted, archived). -- **User interactions**. Click all links, buttons, and interactive elements to ensure they work as expected and navigate to the correct destinations. +- **Page targeting.** Verify that guides appear on the correct pages by testing different URLs and path patterns in your activation rules. +- **User targeting.** Confirm that targeting conditions work correctly by testing with users who should and shouldn't see the guide based on your eligibility criteria. +- **Engagement tracking events.** Interact with your guides and verify that engagement status updates appear as expected in the dashboard (seen, interacted, archived). +- **User interactions.** Click all links, buttons, and interactive elements to ensure they work as expected and navigate to the correct destinations. -While testing, take advantage of the "unarchive" option in the dashboard to reset guide messages, allowing you to test the same user/guide combination multiple times. +While testing, take advantage of the "unarchive" option in the dashboard to reset guide messages, enabling you to test the same user/guide combination multiple times. ## Debugging guides @@ -89,13 +133,13 @@ The easiest way to confirm if a user is eligible for a guide is to call the [Gui If your guide is missing from the response: -- **Confirm the guide is activated**. Verify the guide is committed and active in the environment you're testing. -- **Check if the guide is archived**. Verify the guide hasn't been archived for that user by checking the message status in the dashboard. -- **Review targeting conditions**. Check that user properties match your [targeting rules](/in-app-ui/guides/create-guides#targeting). -- **Verify audience membership**. Ensure the user belongs to any required [audiences](/concepts/audiences). -- **Check activation rules**. Confirm the current page matches your path-based [activation rules](/in-app-ui/guides/create-guides#activation). -- **Validate data types**. Ensure string vs. number comparisons in conditions are correct. -- **Test runtime data**. Verify any data passed to the guides API matches your targeting conditions. +- **Confirm the guide is activated.** Verify the guide is committed and active in the environment you're testing. +- **Check if the guide is archived.** Verify the guide hasn't been archived for that user by checking the message status in the dashboard. +- **Review targeting conditions.** Check that user properties match your [targeting rules](/in-app-ui/guides/create-guides#targeting). +- **Verify audience membership.** Ensure the user belongs to any required [audiences](/concepts/audiences). +- **Check activation rules.** Confirm the current page matches your path-based [activation rules](/in-app-ui/guides/create-guides#activation). +- **Validate data types.** Ensure string vs. number comparisons in conditions are correct. +- **Test runtime data.** Verify any data passed to the guides API matches your targeting conditions. ### Guide provider @@ -107,20 +151,8 @@ If you've confirmed user eligibility for the guide, you may need to debug an iss - The `key` or `type` filter is set incorrectly. - The fetched guide is not being rendered correctly by your component. - The [message type](/in-app-ui/message-types) schema for the guide is formatted differently than expected. -- Guide websocket is not established and realtime updates are unavailable. +- The guide's WebSocket is not established and real-time updates are unavailable. ### Engagement tracking -The expected results of engagment status updates may vary based on your own implementation decisions. While Knock's pre-built guide components provide out-of-the-box engagement tracking, your team will own the UI of other guide components completely. This includes making decisions about and implementing [engagement tracking](/in-app-ui/guides/handling-engagement#tracking-guide-engagement). - -**Common engagement tracking issues:** - -- Engagement status updates have not been implemented for the component. -- Engagement status requests fail because required parameters are missing or incorrect: - - `channel_id` (string, required): The unique identifier for the channel. - - `guide_id` (string, required): The unique identifier for the guide. - - `guide_key` (string, required): The key of the guide. - - `guide_step_ref` (string, required): The step reference of the guide. -- Engagement status requests fail because the `guide_id` is used in the endpoint instead of `message_id`. - -[API reference ->](/api-reference/users/guides/mark_message_as_seen) +The expected results of engagement status updates may vary based on your own implementation decisions. While Knock's pre-built guide components provide out-of-the-box engagement tracking, your team will own the UI of other guide components completely. This includes designing and implementing [engagement tracking](/in-app-ui/guides/handling-engagement#tracking-guide-engagement). diff --git a/content/in-app-ui/guides/render-guides.mdx b/content/in-app-ui/guides/render-guides.mdx index 61447abcb..a751ed3a1 100644 --- a/content/in-app-ui/guides/render-guides.mdx +++ b/content/in-app-ui/guides/render-guides.mdx @@ -7,6 +7,14 @@ section: Building in-app UI > Guides Once you've [created a guide](/in-app-ui/guides/create-guides), you'll need to render it in your product using the [guides API](/api-reference/users/guides/). This involves fetching the guide data from Knock and displaying it to your users based on their eligibility. You can render guides using Knock's client-side SDKs, which provide built-in state management and helper methods, or by directly calling the [guides API](/api-reference/users/guides) to build a custom implementation. Below we cover both approaches and how to filter guides to show the right content to your users. +## Guide identifiers + +There are three identifiers you'll work with when implementing guides: + +- **Guide channel ID.** Guides are delivered through a dedicated channel called "In-app guide". You can find your guide channel ID under [Channels and sources](/integrations/overview) in your Knock dashboard. You'll use this UUID to initialize the guides provider in your client SDK or to call the [guides API](/api-reference/users/guides/) directly. +- **Guide key.** The `key` is a unique identifier for a specific guide instance, set when [creating the guide](/in-app-ui/guides/create-guides). E.g., `subscription-expiration-banner` or `spring-promo-modal`. +- **Guide type.** The `type` of a guide corresponds to the key of its [message type](/in-app-ui/message-types), selected when creating the guide. E.g., `banner` or `custom-modal`. + ## Fetching guides There are two ways to fetch and render guides: using Knock's client-side SDKs, which provide built-in state management and helper methods, or by directly calling the [guides API](/api-reference/users/guides) to build a custom implementation. Our [step-by-step React example](/in-app-ui/react/headless/guide) demonstrates how to incorporate the SDK's guide provider into your application and render guide components to your users. @@ -51,6 +59,8 @@ Knock exposes a set of client SDKs that provide helpers and logic to make it eas ]} /> +For a step-by-step example of how to implement guides with the JavaScript SDK in a Vue.js application, see the [Guides in Vue.js](/tutorials/guides-in-vue) tutorial. + #### Key SDK resources When working with Knock's SDKs to fetch and render guides, you'll use the following components and hooks for React: @@ -99,7 +109,9 @@ When multiple guides exist for a single `type` (e.g, a generic "Banner" componen The [`useGuide`](/in-app-ui/react/sdk/hooks/use-guide) hook returns a single guide at a time, subject to eligibility criteria, throttling rules, and ordering. This is ideal for components that should only display one guide, like a banner or modal that occupies a specific location in your UI. -The [`useGuides`](/in-app-ui/react/sdk/hooks/use-guides) hook fetches one or more guides, subject to eligibility criteria, in the configured order. This is useful for components that can display multiple guides at once, like a list of changelog cards or announcements in a sidebar. The `useGuides` hook is designed to return multiple guides, so it does not apply throttling rules. +The [`useGuides`](/in-app-ui/react/sdk/hooks/use-guides) hook fetches one or more guides, subject to eligibility criteria, in the configured order. This is useful for components that can display multiple guides at once, like a list of changelog cards or announcements in a sidebar. + +Starting in version 0.10.0 of `@knocklabs/react`, `useGuides` respects [throttling rules](/in-app-ui/guides/order-guides#guide-throttling) by default, similar to `useGuide`. If you need to return all eligible guides regardless of throttling, you can pass `includeThrottled: true` as an option. See the [useGuides](/in-app-ui/react/sdk/hooks/use-guides) hook documentation for more details. @@ -126,7 +138,7 @@ The [`useGuides`](/in-app-ui/react/sdk/hooks/use-guides) hook fetches one or mor This approach is useful for displaying lists of announcements, changelog entries, or other content where multiple guides should be visible simultaneously. You can filter guides by `type` to get all guides using a specific message type, or omit the filter to get all eligible guides for the user. - **Note:** `useGuides` returns guides ordered per the priority set in the dashboard, but it bypasses [throttling rules](/in-app-ui/guides/order-guides#guide-throttling) since it's intended to return multiple guides. + **Note:** Starting in version 0.10.0 of `@knocklabs/react`, `useGuides` respects [throttling rules](/in-app-ui/guides/order-guides#guide-throttling) by default. If you want to return all eligible guides regardless of throttling, pass `includeThrottled: true` as an option. diff --git a/content/in-app-ui/ios/sdk/overview.mdx b/content/in-app-ui/ios/sdk/overview.mdx index 17c79cb66..f08335325 100644 --- a/content/in-app-ui/ios/sdk/overview.mdx +++ b/content/in-app-ui/ios/sdk/overview.mdx @@ -13,7 +13,7 @@ The Knock Swift SDK is a client-side SDK for interacting with the Knock API and ## Example app -You can find a complete iOS example application that uses the Swift SDK [here](https://github.com/knocklabs/ios-example-app). The app shows patterns for handling push token registration, building an in-app feed using SwiftUI, and managing user notification preferences. +You can find a complete iOS example application that uses the Swift SDK here. The app shows patterns for handling push token registration, building an in-app feed using SwiftUI, and managing user notification preferences. ## Need help? @@ -30,4 +30,4 @@ Our Swift SDK is worked on full-time by the Knock Mobile team. ### Contributing -All contributors are welcome, from casual to regular. Feel free to open a [pull request](https://github.com/knocklabs/knock-swift/pulls/new). +All contributors are welcome, from casual to regular. Feel free to open a pull request. diff --git a/content/in-app-ui/ios/sdk/push-notifications.mdx b/content/in-app-ui/ios/sdk/push-notifications.mdx index ed34c04bc..f439adf8b 100644 --- a/content/in-app-ui/ios/sdk/push-notifications.mdx +++ b/content/in-app-ui/ios/sdk/push-notifications.mdx @@ -4,23 +4,23 @@ description: "Usage guides to help you get started with the Push Notifications i section: SDKs --- -**Note:** We Recommend taking advantage of our [KnockAppDelegate](/in-app-ui/ios/sdk/reference#knockappdelegate) to make managing your Push Notifications simpler. +**Note:** We recommend taking advantage of our [KnockAppDelegate](/in-app-ui/ios/sdk/reference#knockappdelegate) to make managing your push notifications simpler. ## Prerequisites Before proceeding, ensure you've configured push notifications within your Knock account. For guidance on this initial setup, refer to our [Push Notification Configuration Guide](/integrations/push/overview). -## Step 1: Enabling Push Notifications in Your App +## Step 1: Enabling push notifications in your app -1. **Configure APNs in Your App:** +1. **Configure APNs in your app.** - Open your project in Xcode. - Navigate to your app target's **Signing & Capabilities** tab. - Click the "+" capability button and add **Push Notifications** to enable Apple Push Notification service (APNs). -2. **Enable Background Modes:** +2. **Enable background modes.** - Still in the **Signing & Capabilities** tab, add the **Background Modes** capability. - - Check **Remote notifications** to allow your app to receive silent push notifications. + - Check **Remote notifications** to enable your app to receive silent push notifications. -## Step 2: Registering for Push Notifications +## Step 2: Registering for push notifications Implement the following in your AppDelegate or SceneDelegate to register for push notifications: @@ -62,7 +62,7 @@ class MyAppDelegate: UIResponder, UIApplicationDelegate { } ``` -## Step 3: Updating the Message Status of a Push Notification. +## Step 3: Updating the message status of a push notification If a push notification is sent via Knock, it will contain a `knock_message_id` property that includes the corresponding message Id. This can then be used to update the message status. @@ -83,13 +83,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } ``` -## Step 4: Configuring Silent Push Notifications +## Step 4: Configuring silent push notifications Silent push notifications allow your app to update content in the background without alerting the user. Ensure that your Knock APNs message template has silent notifications enabled. here for more information on installation. ### Initializing a Knock instance diff --git a/content/in-app-ui/javascript/sdk/overview.mdx b/content/in-app-ui/javascript/sdk/overview.mdx index db65215cb..dae017650 100644 --- a/content/in-app-ui/javascript/sdk/overview.mdx +++ b/content/in-app-ui/javascript/sdk/overview.mdx @@ -41,4 +41,4 @@ Ask questions and find answers on the following platforms: ### Contributing -All contributors are welcome, from casual to regular. Feel free to open a [pull request](https://github.com/knocklabs/javascript/pulls). +All contributors are welcome, from casual to regular. Feel free to open a pull request. diff --git a/content/in-app-ui/javascript/sdk/quick-start.mdx b/content/in-app-ui/javascript/sdk/quick-start.mdx index 9f016f87e..873ab9fd6 100644 --- a/content/in-app-ui/javascript/sdk/quick-start.mdx +++ b/content/in-app-ui/javascript/sdk/quick-start.mdx @@ -14,7 +14,7 @@ The `@knocklabs/client` library is a low-level JavaScript SDK for interacting wi ## Getting started -To use this example, you'll need [an account on Knock](https://dashboard.knock.app), as well as an in-app feed channel, with a workflow that produces in-app feed messages. You'll also need: +To use this example, you'll need an account on Knock, as well as an in-app feed channel, with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) @@ -33,7 +33,7 @@ import Knock from "@knocklabs/client"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); // Tell Knock to use the users id -knockClient.authenticate(currentUser.id); +knockClient.authenticate({ id: currentUser.id }); ``` ## Initialize a feed connection for the user diff --git a/content/in-app-ui/message-types/schema-reference.mdx b/content/in-app-ui/message-types/schema-reference.mdx index e61c2abc5..5b44a8708 100644 --- a/content/in-app-ui/message-types/schema-reference.mdx +++ b/content/in-app-ui/message-types/schema-reference.mdx @@ -501,3 +501,53 @@ Every image field contains three subfields: } } ``` + +### JSON + +A JSON input field with schema validation. + +#### Settings + + + + + + + + +#### Example + +```json +{ + "type": "json", + "key": "data", + "label": "Data", + "settings": { + "description": "A description of the JSON field", + "required": true, + "default": { "key": "value" }, + "schema": { + "properties": { + "key": { "type": "string" } + }, + "required": ["key"] + } + } +} +``` diff --git a/content/in-app-ui/react-native/sdk/overview.mdx b/content/in-app-ui/react-native/sdk/overview.mdx index 25c19aa28..35f26eff0 100644 --- a/content/in-app-ui/react-native/sdk/overview.mdx +++ b/content/in-app-ui/react-native/sdk/overview.mdx @@ -36,7 +36,7 @@ Using the React Native SDK it's possible to build: ## Example app -You can find a basic example application that uses the React Native SDK [here](https://github.com/knocklabs/javascript/tree/main/examples/expo-example). The app shows patterns for handling push token registration, building an in-app feed, and managing user notification preferences. +You can find a basic example application that uses the React Native SDK here. The app shows patterns for handling push token registration, building an in-app feed, and managing user notification preferences. ## Need help? @@ -55,4 +55,4 @@ Ask questions and find answers on the following platforms: ### Contributing -All contributors are welcome, from casual to regular. Feel free to open a [pull request](https://github.com/knocklabs/javascript/pulls/new). +All contributors are welcome, from casual to regular. Feel free to open a pull request. diff --git a/content/in-app-ui/react/banner.mdx b/content/in-app-ui/react/banner.mdx index 7bd1dc74d..12bd2b73f 100644 --- a/content/in-app-ui/react/banner.mdx +++ b/content/in-app-ui/react/banner.mdx @@ -16,8 +16,7 @@ The banner component enables you to display important notifications, alerts, or alt="BannerComponent" width={1356} height={1000} - className="mx-auto border border-gray-200" - style={{ borderRadius: "var(--tgph-rounded-5)" }} + className="mx-auto border border-gray-200 rounded-xl" /> ## Getting started @@ -122,8 +121,7 @@ The pre-built banner message type supports three variants for different use case alt="BannerVariants" width={1506} height={466} - className="mx-auto border border-gray-200 w-full" - style={{ borderRadius: "var(--tgph-rounded-5)" }} + className="mx-auto border border-gray-200 rounded-xl w-full" /> ## Handling user engagement diff --git a/content/in-app-ui/react/card.mdx b/content/in-app-ui/react/card.mdx index 780e44311..0855ad660 100644 --- a/content/in-app-ui/react/card.mdx +++ b/content/in-app-ui/react/card.mdx @@ -16,8 +16,7 @@ The card component enables you to display contextual information, tips, or inter alt="CardComponent" width={1356} height={1000} - className="mx-auto border border-gray-200" - style={{ borderRadius: "var(--tgph-rounded-5)" }} + className="mx-auto border border-gray-200 rounded-xl" /> ## Getting started @@ -133,8 +132,7 @@ The pre-built card message type supports three variants for different use cases: alt="CardVariants" width={1506} height={466} - className="mx-auto border border-gray-200 w-full" - style={{ borderRadius: "var(--tgph-rounded-5)" }} + className="mx-auto border border-gray-200 rounded-xl w-full" /> ## Handling user engagement diff --git a/content/in-app-ui/react/feed.mdx b/content/in-app-ui/react/feed.mdx index 19bde07f4..444d03e65 100644 --- a/content/in-app-ui/react/feed.mdx +++ b/content/in-app-ui/react/feed.mdx @@ -9,20 +9,19 @@ section: Building in-app UI Our `@knocklabs/react` library comes pre-built with a real-time feed component that you can drop into your application. This page provides common recipes to help you work with the pre-built Knock feed UI element. -[See a live demo of our pre-built in-app feed UI element ->](https://in-app-demo.knock.app/) +See a live demo of our pre-built in-app feed UI element. ## Getting started -To use this example, you'll need [an account on Knock](https://dashboard.knock.app), as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: +To use this example, you'll need an account on Knock, as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) @@ -177,11 +176,11 @@ import { KnockFeedProvider } from "@knocklabs/react"; You can customize the appearance of the feed components in two main ways: -1. **CSS variables**: The feed uses CSS variables, which you can override to align with your product's design. Check the [theme.css source code](https://github.com/knocklabs/javascript/blob/main/packages/react/src/theme.css) for available variables. +1. **CSS variables**: The feed uses CSS variables, which you can override to align with your product's design. Check the theme.css source code for available variables. -2. **Custom CSS**: You can completely replace the Knock CSS to tailor the feed's look. All feed classes are unique and start with `rnf-`. View the [component source code](https://github.com/knocklabs/javascript/tree/main/packages/react/src) for class details. +2. **Custom CSS**: You can completely replace the Knock CSS to tailor the feed's look. All feed classes are unique and start with `rnf-`. View the component source code for class details. -If you import the component's CSS styles, you can use [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) to override most properties like colors and font sizes. These variables are prefixed with `--rnf-`. Refer to the [theme.css file](https://raw.githubusercontent.com/knocklabs/javascript/main/packages/react/src/theme.css) for a complete list. +If you import the component's CSS styles, you can use CSS variables to override most properties like colors and font sizes. These variables are prefixed with `--rnf-`. Refer to the theme.css file for a complete list. ## Handling interactivity diff --git a/content/in-app-ui/react/headless/feed.mdx b/content/in-app-ui/react/headless/feed.mdx index df89b5151..b1d2dcf5d 100644 --- a/content/in-app-ui/react/headless/feed.mdx +++ b/content/in-app-ui/react/headless/feed.mdx @@ -17,7 +17,7 @@ To use this example, you'll need [an account on Knock](https://dashboard.knock.a - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) @@ -52,7 +52,7 @@ const App = ({ user }) => ( ); ``` -## Setup the Knock client +## Initialize the Knock client Next, we'll need to access the instance of the Knock client created by the `KnockProvider` using the `useKnockClient` hook. @@ -66,7 +66,7 @@ const NotificationFeed = ({ user }) => { }; ``` -## Setup the Knock feed instance +## Initialize the feed instance Next, we'll want to set up an instance of a Knock Feed, which will handle the state management and provide a way for us to interact with the messages on the feed. @@ -95,6 +95,25 @@ const NotificationFeed = ({ user }) => { }; ``` +### Feed mode + +By default, the feed is initialized in `compact` mode, which returns a leaner payload with some lesser-used fields omitted. See [FeedItem](/in-app-ui/api-overview#feeditem) for more details on the payload schema. + +| Mode | Behavior | +| --------- | -------------------------------------------------------------------------------------------------------------------- | +| `compact` | Default. Omits `activities`, `total_activities`, and all but one actor. Omits nested arrays and objects from `data`. | +| `rich` | Returns the full payload of each `FeedItem`. | + +If your application needs the full payload, pass `{ mode: "rich" }` as the third argument to `useNotifications`: + +```jsx title="Initialize the feed in rich mode" +const feedClient = useNotifications( + knockClient, + process.env.KNOCK_FEED_CHANNEL_ID, + { mode: "rich" }, +); +``` + ## Creating a custom notifications UI The last step is to render our notifications UI using the data that's exposed via the state store (`items` and `metadata`). diff --git a/content/in-app-ui/react/modal.mdx b/content/in-app-ui/react/modal.mdx index 4717760fc..77899ed9b 100644 --- a/content/in-app-ui/react/modal.mdx +++ b/content/in-app-ui/react/modal.mdx @@ -16,8 +16,7 @@ The modal component enables you to display important notifications, announcement alt="ModalComponent" width={1356} height={1000} - className="mx-auto border border-gray-200" - style={{ borderRadius: "var(--tgph-rounded-5)" }} + className="mx-auto border border-gray-200 rounded-xl" /> ## Getting started @@ -122,8 +121,7 @@ The pre-built modal message type supports three variants for different use cases alt="ModalVariants" width={1506} height={466} - className="mx-auto border border-gray-200 w-full" - style={{ borderRadius: "var(--tgph-rounded-5)" }} + className="mx-auto border border-gray-200 rounded-xl w-full" /> ## Handling user engagement diff --git a/content/in-app-ui/react/preferences.mdx b/content/in-app-ui/react/preferences.mdx index c46a3b382..cc793695a 100644 --- a/content/in-app-ui/react/preferences.mdx +++ b/content/in-app-ui/react/preferences.mdx @@ -5,7 +5,7 @@ description: How to build a complete notification preference center, powered by section: Building in-app UI --- -This page covers how to build a `PreferenceCenter` React component with Knock's preference APIs. This component should be flexible enough to handle most of your needs and can easily be customized or extended for more specific use cases. If you want to reference a TypeScript example, you can find one in the [Notion feed example](https://github.com/knocklabs/notion-feed-example/blob/main/components/PreferenceCenter.tsx). +This page covers how to build a `PreferenceCenter` React component with Knock's preference APIs. This component should be flexible enough to handle most of your needs and can easily be customized or extended for more specific use cases. If you want to reference a TypeScript example, you can find one in the Notion feed example. @@ -488,7 +488,7 @@ Knock's preference model is very flexible, but you should find that the componen import Knock from "@knocklabs/client"; import { useEffect, useState } from "react"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); -knockClient.authenticate(currentUser.id); +knockClient.authenticate({ id: currentUser.id }); // Here we create a view config object, this helps us customize the interface // and choose which preference options we want to display to the user diff --git a/content/in-app-ui/react/sdk/migrating-from-react-notification-feed.mdx b/content/in-app-ui/react/sdk/migrating-from-react-notification-feed.mdx index 29a59cebd..64d48d9af 100644 --- a/content/in-app-ui/react/sdk/migrating-from-react-notification-feed.mdx +++ b/content/in-app-ui/react/sdk/migrating-from-react-notification-feed.mdx @@ -6,7 +6,7 @@ section: Building in-app UI This documentation will walk you through the steps of replacing `@knocklabs/react-notification-feed` with `@knocklabs/react`. -Please [report any issues](https://github.com/knocklabs/javascript/issues/new) you encounter while upgrading. +Please report any issues you encounter while upgrading. ## Installation diff --git a/content/in-app-ui/react/sdk/overview.mdx b/content/in-app-ui/react/sdk/overview.mdx index e3d0a86e8..ccd51e477 100644 --- a/content/in-app-ui/react/sdk/overview.mdx +++ b/content/in-app-ui/react/sdk/overview.mdx @@ -23,7 +23,7 @@ Our [`@knocklabs/react`](https://github.com/knocklabs/javascript/tree/main/packa The React library is built on-top of the `@knocklabs/client` JS SDK and includes that library as an implicit dependency. -[See a live demo](https://in-app-demo.knock.app/) +See a live in-app demo. **You can also use the library to build:** @@ -58,4 +58,4 @@ Ask questions and find answers on those following platforms: ### Contributing -All contributors are welcome, from casual to regular. Feel free to open a [pull request](https://github.com/knocklabs/javascript/pulls/new). +All contributors are welcome, from casual to regular. Feel free to open a pull request. diff --git a/content/in-app-ui/react/slack-kit.mdx b/content/in-app-ui/react/slack-kit.mdx index 72a50788a..6a895a368 100644 --- a/content/in-app-ui/react/slack-kit.mdx +++ b/content/in-app-ui/react/slack-kit.mdx @@ -279,7 +279,7 @@ const ConnectedChannelsList = ({ slackChannelsRecipientObject }) => { ### Client functions -If you want more fine grain control of your data, you can skip the hooks and simply use the functions Knock exposes in the [`@knocklabs/client` library](https://github.com/knocklabs/javascript/tree/main/packages/client) as long as you wrap the component you're calling it in inside of `KnockProvider`. You can accomplish anything we provide with hooks or the components with the following functions: +If you want more fine grain control of your data, you can skip the hooks and simply use the functions Knock exposes in the @knocklabs/client library as long as you wrap the component you're calling it in inside of `KnockProvider`. You can accomplish anything we provide with hooks or the components with the following functions: - `knock.slack.authCheck`: Get the status of Slack authorization - `knock.slack.getChannels`: Get a list of Slack channels for the given tenant @@ -343,7 +343,7 @@ You'll need to pass this token along with the public API key to the `KnockProvid ### Other languages -If you're not using the Node SDK, you can still generate a user token using a JWT signing library in your preferred language. Here's an [example of using Joken for the Elixir library](https://github.com/knocklabs/knock-elixir?tab=readme-ov-file#signing-jwts). You'll include the `grants` key in the root of the payload and put your resource grants in there. We'll go into detail about how they work below, but if you want to skip that and just get started, here's what that will look like in a given JWT payload for the example above: +If you're not using the Node SDK, you can still generate a user token using a JWT signing library in your preferred language. Here's an example of using Joken for the Elixir library. You'll include the `grants` key in the root of the payload and put your resource grants in there. We'll go into detail about how they work below, but if you want to skip that and just get started, here's what that will look like in a given JWT payload for the example above: ```json { @@ -378,7 +378,7 @@ These resources need different permissions. Here are the permissions needed for ### How the grants are structured -Resource access grants in Knock are structured according to the [UCAN spec](https://github.com/ucan-wg/spec). They consist of an array of maps, with each map representing a resource. +Resource access grants in Knock are structured according to the UCAN spec. They consist of an array of maps, with each map representing a resource. How to read a resource grant: diff --git a/content/in-app-ui/react/teams-kit.mdx b/content/in-app-ui/react/teams-kit.mdx index 4e8486a7c..bb41d67bd 100644 --- a/content/in-app-ui/react/teams-kit.mdx +++ b/content/in-app-ui/react/teams-kit.mdx @@ -262,7 +262,7 @@ To use a hook in your component, all you need to do is import it and pass it the ### Client functions -If you want more fine grain control of your data, you can skip the hooks and simply use the functions Knock exposes in the [`@knocklabs/client` library](https://github.com/knocklabs/javascript/tree/main/packages/client) as long as you wrap the component you're calling it in inside of `KnockProvider`. You can accomplish anything we provide with hooks or the components with the following functions: +If you want more fine grain control of your data, you can skip the hooks and simply use the functions Knock exposes in the @knocklabs/client library as long as you wrap the component you're calling it in inside of `KnockProvider`. You can accomplish anything we provide with hooks or the components with the following functions: - `knock.msTeams.authCheck`: Get the status of Microsoft Teams authorization - `knock.msTeams.getTeams`: Get a list of teams in the connected Microsoft Entra tenant @@ -328,7 +328,7 @@ You'll need to pass this token along with the public API key to the `KnockProvid ### Other languages -If you're not using the Node SDK, you can still generate a user token using a JWT signing library in your preferred language. Here's an [example of using Joken for the Elixir library](https://github.com/knocklabs/knock-elixir?tab=readme-ov-file#signing-jwts). You'll include the `grants` key in the root of the payload and put your resource grants in there. We'll go into detail about how they work below, but if you want to skip that and just get started, here's what that will look like in a given JWT payload for the example above: +If you're not using the Node SDK, you can still generate a user token using a JWT signing library in your preferred language. Here's an example of using Joken for the Elixir library. You'll include the `grants` key in the root of the payload and put your resource grants in there. We'll go into detail about how they work below, but if you want to skip that and just get started, here's what that will look like in a given JWT payload for the example above: ```json { @@ -363,7 +363,7 @@ These resources need different permissions. Here are the permissions needed for ### How the grants are structured -Resource access grants in Knock are structured according to the [UCAN spec](https://github.com/ucan-wg/spec). They consist of an array of maps, with each map representing a resource. +Resource access grants in Knock are structured according to the UCAN spec. They consist of an array of maps, with each map representing a resource. How to read a resource grant: diff --git a/content/in-app-ui/react/toasts.mdx b/content/in-app-ui/react/toasts.mdx index 7467b93f1..0e2eaee13 100644 --- a/content/in-app-ui/react/toasts.mdx +++ b/content/in-app-ui/react/toasts.mdx @@ -6,11 +6,11 @@ section: Building in-app UI While there are no out-of-the-box toast components in the `@knocklabs/react` library, it's easy to build toasts on top of the primitives exposed. This page covers how to do that using the `react-hot-toasts` library as our "toaster." -[See a live demo](https://in-app-demo.knock.app/) +See a live demo. ## Getting started -To use this example, you'll need [an account on Knock](https://dashboard.knock.app), as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: +To use this example, you'll need an account on Knock, as well as an in-app feed channel with a workflow that produces in-app feed messages. You'll also need: - A public API key for the Knock environment (set as `KNOCK_PUBLIC_API_KEY`) - The channel ID for the in-app feed (set as `KNOCK_FEED_CHANNEL_ID`) diff --git a/content/in-app-ui/security-and-authentication.mdx b/content/in-app-ui/security-and-authentication.mdx index 4b9b55cf1..193363fb8 100644 --- a/content/in-app-ui/security-and-authentication.mdx +++ b/content/in-app-ui/security-and-authentication.mdx @@ -18,7 +18,11 @@ tags: text={ <> This documentation references examples from our{" "} - + client-side JS SDK . You only need to add the authentication outlined here if you're integrating @@ -28,20 +32,124 @@ tags: } /> -Access to Knock's API is protected using a secret API key for your backend application, and a public API key for your client application. -By default, the public API key can read in-app feeds and manage user preferences for any of your users. While this is convenient for testing and development, this is not suitable for production environments. +Access to Knock's [API](/api-reference) is protected using a secret API key for your backend application, and a public API key for your client application. -Production environments should enable **enhanced security mode**, which requires clients to send both the public API key and a signed user token that identifies the user that is performing the request. Enhanced security mode trades convenience for security, and we recommend that you enable it [when going to production](/tutorials/implementation-guide#going-to-production). - -## API endpoints that can be called with a public API key +## Public API key endpoints -Public API keys can make the following calls from your client application: +By default a public API key within Knock can call the following endpoints on the API (see table below). -- Fetching a user's notification feed -- Marking a feed message as read, seen, or archived -- Getting or setting a user's preferences +Production environments should enable **enhanced security mode**, which requires clients to send both the public API key and a signed user token that identifies the user that is performing the request. Enhanced security mode trades convenience for security, and we recommend that you enable it [when going to production](/tutorials/implementation-guide#going-to-production). -When enhanced security mode is enabled, calls from your client application will need to include both the public API key as well as a **signed user token**. +Further, it's possible to [limit the endpoints](#limiting-access-to-specific-resources) that can be called with a public API key by setting access `grants` on the user token. This can also be used in [multi-tenant environments](/multi-tenancy/overview) to restrict access to specific tenants that the user has access to. + +
Retrieve a user by their ID, + "GET", + "/v1/users/:id", + ], + [ + Update a user by their ID, + "PUT", + "/v1/users/:id", + ], + [ + + Retrieve the user's preferences by their ID + , + "GET", + "/v1/users/:id/preferences", + ], + [ + + Retrieve a specific preference by the user's ID and preference set ID + , + "GET", + "/v1/users/:id/preferences/:id", + ], + [ + + Update a specific preference by the user's ID and preference set ID + , + "PUT", + "/v1/users/:id/preferences/:id", + ], + [ + + Retrieve the channel data for a specific user on a specific channel + , + "GET", + "/v1/users/:id/channel_data/:channel_id", + ], + [ + + Update the channel data for a specific user on a specific channel + , + "PUT", + "/v1/users/:id/channel_data/:channel_id", + ], + [ + + Delete the channel data for a specific user on a specific channel + , + "DELETE", + "/v1/users/:id/channel_data/:channel_id", + ], + [ + + Retrieve in-app feed messages for a specific user on a specific channel + , + "GET", + "/v1/users/:id/feeds/:channel_id", + ], + [ + + Mark an in-app feed message as read, seen, or archived + , + "PUT", + "/v1/messages/:message_id/:status", + ], + [ + + Unmark an in-app feed message as read, seen, or archived + , + "DELETE", + "/v1/messages/:message_id/:status", + ], + [ + + Mark multiple in-app feed messages as read, seen, or archived + , + "POST", + "/v1/messages/batch/:status", + ], + [ + + Perform a bulk action on multiple in-app feed messages for a specific + channel + , + "POST", + "/v1/channels/:channel_id/messages/bulk/:action", + ], + [ + + Retrieve guides for a specific user on a specific channel + , + "GET", + "/v1/users/:id/guides/:channel_id", + ], + [ + + Mark a guide message as seen, interacted, or archived + , + "PUT", + "/v1/users/:id/guides/:channel_id/messages/:message_id/:status", + ], + ]} +/> ## Authentication (without enhanced security) @@ -157,7 +265,7 @@ import Knock from "@knocklabs/client"; const knockClient = new Knock(process.env.KNOCK_PUBLIC_API_KEY); // Tell Knock to use the user id and the token -knockClient.authenticate(currentUser.id, currentUser.knockToken); +knockClient.authenticate({ id: currentUser.id }, currentUser.knockToken); ``` **React notification feed example** @@ -170,6 +278,160 @@ knockClient.authenticate(currentUser.id, currentUser.knockToken); > ``` +## Limiting access to specific resources + +You can limit access to the endpoints that can be called with a public API key by setting access `grants` on the user token. + +You can ensure that **no other actions** can be taken on any other resources by setting the `explicit_grants` option to `true` when generating the user token. Without this option, the token will be granted access to all resources by default. + +
+ +Grants are structured according to the UCAN spec. They consist of an array of maps, with each map representing a resource. + +A grant for a user should always be structured with the user's ID as the key (`https://api.knock.app/v1/users/`). This should always match the `sub` claim in the user token. + +```json title="Example of a grant for a user" +{ + "https://api.knock.app/v1/users/": { + "user/read": [{}] + } +} +``` + +```json title="Example of a user token with grants to make it read-only" +{ + "sub": "", + "explicit_grants": true, + "grants": { + "https://api.knock.app/v1/users/": { + "user/read": [{}], + "feed/read": [{}], + "preferences/read": [{}], + "channel_data/read": [{}] + } + } +} +``` + +### Scoping a token to a tenant + +Some per-tenant resources can further be scoped to a specific tenant by including a set of grants for one or more tenants in the user token. Doing so will restrict the updates that can be made to the resource to only the tenants that are included in the user token. + + + Without a tenant-scope, any user actions will apply to all tenants. When + at-least one tenant-scope is included, any user actions will apply to the + tenants included in the user token. + + } +/> + +Per-tenant resources can be applied to: + +
+ +Grants for a tenant should always be structured with the tenant's ID as the key (`https://api.knock.app/v1/tenants/`). One or more tenant grants can be included in the user token. + +**Example policies** + +This policy will **only** allow the user to read in-app feeds for the tenant `acme_corp`. Feed reads to any other tenant will be denied, as will feed reads to a nil tenant scope. + +```json title="Example of a user token with grants for a specific tenant" +{ + "sub": "", + "explicit_grants": true, + "grants": { + "https://api.knock.app/v1/tenants/acme_corp": { + "user_feed/read": [{}] + } + } +} +``` + +To allow the user to read a feed for the unscoped usecase (e.g. tenant = null), and also allow them to read feeds for the tenant `acme_corp`, you can include a grant the user's resource and a grant for the tenant. + +```json title="Example of a user token with grants for a specific tenant" +{ + "sub": "", + "explicit_grants": true, + "grants": { + "https://api.knock.app/v1/users/": { + "user_feed/read": [{}] + }, + "https://api.knock.app/v1/tenants/acme_corp": { + "user_feed/read": [{}] + } + } +} +``` + ## Handling token expiration Generally, it's advisable to set the token expiration to be equal to your session token expiration. That way you can regenerate both of the tokens together from within your application. There may, however, be cases where this is not possible, in which case it's best practice to opt for a relatively short-lived expiration time for your user tokens and refresh them from your backend before the expiration window. diff --git a/content/integrations/chat/discord.mdx b/content/integrations/chat/discord.mdx index 581c0e1ae..289f6fd79 100644 --- a/content/integrations/chat/discord.mdx +++ b/content/integrations/chat/discord.mdx @@ -18,7 +18,7 @@ We support two main methods for sending notifications to Discord: Incoming webhooks are simpler in terms of configuration and will probably serve best for most cases but if that's not enough, using bots should provide enough flexibility for all use cases. -The main difference with both approaches is that for incoming webhooks, you need to create one per Discord channel. If you are planning to notify just a few Discord channels, this is probably the easiest, but if you need to notify multiple Discord channels the bot approach will work best. Additionally, [Message Components](https://discord.com/developers/docs/interactions/message-components) cannot be used with the incoming webhook approach, so if you're planning on leveraging those you should create a bot. +The main difference with both approaches is that for incoming webhooks, you need to create one per Discord channel. If you are planning to notify just a few Discord channels, this is probably the easiest, but if you need to notify multiple Discord channels the bot approach will work best. Additionally, Message Components cannot be used with the incoming webhook approach, so if you're planning on leveraging those you should create a bot. Next we'll walk through how to configure each of these methods to send Discord notifications using Knock. @@ -76,7 +76,7 @@ Now you're ready to send notifications to Discord via incoming webhook! Jump to #### 1. Create an app and bot -You'll need to set up a bot and app to handle incoming messages from Knock. You can learn more in the documentation for [creating Discord apps](https://discord.com/developers/applications) and [creating Discord bots](https://discord.com/developers/docs/topics/oauth2#bots) (note: you'll need to be logged in to access the applications docs). +You'll need to set up a bot and app to handle incoming messages from Knock. You can learn more in the documentation for creating Discord apps and creating Discord bots (note: you'll need to be logged in to access the applications docs). #### 2. Set OAuth permissions for the bot @@ -95,14 +95,22 @@ Within the Application you've created on Discord, find the URL Generator under `
  • A{" "} - + scope {" "} is a permission granted to a Discord app when it joins a Discord server.
  • - + Bot permissions {" "} dictate what the bot can do in the server. diff --git a/content/integrations/chat/microsoft-teams/overview.mdx b/content/integrations/chat/microsoft-teams/overview.mdx index 1ad824271..f923ca676 100644 --- a/content/integrations/chat/microsoft-teams/overview.mdx +++ b/content/integrations/chat/microsoft-teams/overview.mdx @@ -27,22 +27,13 @@ If you're using a Microsoft Teams bot, make sure your bot has been registered an Additionally, if you intend to use [TeamsKit](/in-app-ui/react/teams-kit) or Knock's [Microsoft Teams-related React hooks](/in-app-ui/react/teams-kit#using-teamskit-headless), you'll need to [configure a Graph API-enabled application in the Microsoft Entra admin center](#configuring-a-graph-api-enabled-application-in-microsoft-entra). - - As of July 31, 2025, Microsoft no longer allows the creation of - multi-tenant bots in Azure. Knock continues to support existing (legacy) - multi-tenant bots. -
    -
    - For new Microsoft Teams integrations, you should register your bot as single-tenant - and publish it to the Microsoft Teams app store as a cross-tenant app so that - it can be installed across customer workspaces. - - } -/> +#### Registering a new bot + +As of July 31, 2025, Microsoft no longer allows the creation of multi-tenant bots in Azure. If you're registering a new bot, you'll need to register it as single-tenant and publish it to the Microsoft Teams app store as a cross-tenant app so it can be installed across customer workspaces. + +The app store submission process is managed entirely by Microsoft and typically takes 4–6 weeks to complete. We recommend reviewing Microsoft's app submission guide ahead of time to understand what's required, as there are several steps involved. + +While your submission is under review, you can test your integration end-to-end by sideloading your app into a tenant directly. Download your Teams app as a `.zip` package from the Teams Developer Portal, then upload it to the Teams Admin Center of the tenant you want to test in under **Teams apps** > **Manage app** > **Upload**. This makes your app available in that tenant's catalog without needing to wait for store approval. ### Add Teams to Knock as a channel diff --git a/content/integrations/chat/microsoft-teams/sending-an-internal-message.mdx b/content/integrations/chat/microsoft-teams/sending-an-internal-message.mdx index 9f227c314..66fc959cf 100644 --- a/content/integrations/chat/microsoft-teams/sending-an-internal-message.mdx +++ b/content/integrations/chat/microsoft-teams/sending-an-internal-message.mdx @@ -31,12 +31,7 @@ Once you have a repository object created, you can add the channel data for Micr ## Objects as workflow recipients -To add channel data, we’ll set up an incoming webhook in Microsoft Teams. There are two ways to generate an incoming webhook URL for a Teams channel: - -1. Your customer can [create an incoming webhook](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook) and send it to you. -2. You can [build a Microsoft 365 Connector](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-creating) that your customers will install in Teams and add to their channels. When a customer adds your Connector to a channel, you receive an incoming webhook URL. - -Whichever method you choose, the end result is the same: an incoming webhook URL. +To add channel data, we’ll set up an incoming webhook in Microsoft Teams using the Workflows app (powered by Power Automate). Follow Microsoft’s documentation on creating webhooks using Workflows to generate a webhook URL for the Teams channel you want to post to. ### Set the webhook as channel data diff --git a/content/integrations/chat/slack/overview.mdx b/content/integrations/chat/slack/overview.mdx index 78359bd85..939ffe717 100644 --- a/content/integrations/chat/slack/overview.mdx +++ b/content/integrations/chat/slack/overview.mdx @@ -30,6 +30,7 @@ Once you create your Slack app, you’ll be routed to its app management page wi width={3248} height={1942} alt="Slack app management page" + border={false} /> If you are new to Slack apps, there are a few key concepts to understand: @@ -84,7 +85,7 @@ In this section we'll complete the steps for setting up your Knock account and S content="https://api.knock.app/providers/slack/authenticate" /> - If you're building your own OAuth flow, you'll need to use a URL in your frontend application. You can reference this documentation on [building a Slack OAuth flow](https://knock.app/blog/how-to-authenticate-users-in-slack-using-oauth) from scratch. + If you're building your own OAuth flow, you'll need to use a URL in your frontend application. You can reference this documentation on building a Slack OAuth flow from scratch. Under **Manage Distribution**, click the "Distribute app" button which will show you a list of items to complete to activate public distribution. If you've built the app from scratch and completed the previous steps, you should see all of these complete except for "Remove hard coded information." @@ -256,8 +257,8 @@ Depending on the Slack integration you build into your product, you’ll store t For this integration, you'll store a user's Slack `user_id` in the `SlackConnection` object. You can [enable email-based user ID resolution](/integrations/chat/slack/sending-a-direct-message#enabling-email-based-user-id-resolution) if you'd like Knock to automatically resolve these IDs on your behalf. Alternatively, you can manually find the correct `user_id` by querying Slack's API: - - by [a given user's email address](https://api.slack.com/methods/users.lookupByEmail) - - for [a list of all of a workspace's users](https://api.slack.com/methods/users.list) + - by a given user's email address + - for a list of all of a workspace's users You'll need to be sure that you request the appropriate [`scopes`](/integrations/chat/slack/overview#set-up-slack-app) for these methods during the auth process. @@ -266,7 +267,7 @@ Depending on the Slack integration you build into your product, you’ll store t If your integration involves a customer connecting a _non-user resource_ in their product (such as a project or a page) to a Slack channel, you’ll want to store that channel data [on an object](/api-reference/objects/set_channel_data) in Knock, as it’s not specific to any single user. You can find the correct `channel_id` and display a list of channels for your user to select from by querying the Slack API: - - for [a list of all of a workspace's channels](https://api.slack.com/methods/conversations.list) + - for a list of all of a workspace's channels The [`SlackChannelCombobox`](/in-app-ui/react/slack-kit#slackchannelcombobox) component of Knock's SlackKit can help you with this. @@ -282,18 +283,24 @@ When you add a new Slack channel step to a workflow in Knock, you'll need to con ### Markdown templates -Editing a markdown template for Slack is just like editing any other markdown-based template in Knock. You can use [Liquid](https://shopify.github.io/liquid/) to inject variables and add control tags (e.g. if-then, for-loop) into your template. +Editing a markdown template for Slack is just like editing any other markdown-based template in Knock. You can use Liquid to inject variables and add control tags (e.g. if-then, for-loop) into your template. Slack uses a markdown-variant syntax called{" "} - + mrkdwn , but Knock handles this for you automatically, so you can write your templates - in good old + in good old {" "} markdown . @@ -316,15 +323,15 @@ There are {{ total_activities }} comments left on {{ page_name }}. [**View page**]({{vars.base_url}}/{{account_id}}/pages/{{ page_id }}) ``` -In the example above we're using [Liquid's for-loop tag](https://shopify.github.io/liquid/tags/iteration/) to iterate over the activities array produced by a Knock batch function. You can learn more about Knock batch functions and the state they produce in our [batch function documentation](/send-notifications/designing-workflows/batch-function). +In the example above we're using Liquid's for-loop tag to iterate over the activities array produced by a Knock batch function. You can learn more about Knock batch functions and the state they produce in our [batch function documentation](/send-notifications/designing-workflows/batch-function). ### Block-based templates -For more advanced layouts in your Slack messages, including images and buttons, you'll need to use Slack's [block kit UI framework](https://api.slack.com/block-kit) to build your notification templates. The block kit framework is a set of different JSON objects you can use together and arrange to create Slack app and notification layouts. +For more advanced layouts in your Slack messages, including images and buttons, you'll need to use Slack's block kit UI framework to build your notification templates. The block kit framework is a set of different JSON objects you can use together and arrange to create Slack app and notification layouts. #### Designing block kit templates -To start you'll want to design your block-based Slack message template. The best way to do this today is to use [Slack's block kit builder](https://app.slack.com/block-kit-builder/). It gives you a drag-and-drop interface for building out your Slack templates, and outputs the JSON you'll need to bring into your Knock template. +To start you'll want to design your block-based Slack message template. The best way to do this today is to use Slack's block kit builder. It gives you a drag-and-drop interface for building out your Slack templates, and outputs the JSON you'll need to bring into your Knock template. Once you've designed your Slack template, copy the JSON from the block kit builder and bring it into your Knock notification template. You'll use the "Switch to JSON editor" button at the bottom right of the Knock template editor page to switch to our JSON editor, and then paste in the JSON you copied from the block kit builder. diff --git a/content/integrations/chat/slack/sending-an-internal-message.mdx b/content/integrations/chat/slack/sending-an-internal-message.mdx index 48256c8c8..7f84a12c6 100644 --- a/content/integrations/chat/slack/sending-an-internal-message.mdx +++ b/content/integrations/chat/slack/sending-an-internal-message.mdx @@ -31,7 +31,7 @@ Once you have a repository object created, you can add the channel data for Slac ## Objects as workflow recipients -To add channel data, we’ll set up an [incoming webhook in Slack](https://api.slack.com/messaging/webhooks). To get this URL from Slack, you can go to β€œIncoming Webhooks” within your dashboard and toggle the "Activate Incoming Webhooks" feature. If you’ve already installed your app into a workspace, you will be asked to reinstall it and select a channel the app will post messages to. +To add channel data, we’ll set up an incoming webhook in Slack. To get this URL from Slack, you can go to β€œIncoming Webhooks” within your dashboard and toggle the "Activate Incoming Webhooks" feature. If you’ve already installed your app into a workspace, you will be asked to reinstall it and select a channel the app will post messages to. Chat layout: integrations --- -Knock integrates with [WhatsApp](https://www.whatsapp.com/) to send notifications to your recipients via the WhatsApp Business API. This integration enables you to send template-based messages directly to your users' WhatsApp accounts, providing a familiar and widely-used communication channel for important notifications. +Knock integrates with WhatsApp to send notifications to your recipients via the WhatsApp Business API. This integration enables you to send template-based messages directly to your users' WhatsApp accounts, providing a familiar and widely-used communication channel for important notifications. ## Features @@ -18,7 +18,7 @@ Before you set up your WhatsApp chat channel in Knock, you'll need to take the f ### 1. Create a business app on Facebook -Login to your [Facebook developer account](https://developers.facebook.com/) and click on the ["Create app"](https://developers.facebook.com/apps/create/) button, +Login to your Facebook developer account and click on the Create app button, then choose the first "Business" type app and complete the details with your personal information. send a test message, you can go to _WhatsApp/first steps_, where you will find the **temporary access token**, **phone number id** and a **curl of send messages**. add a business phone number to send messages from. You can do this using the _Add Phone Number_ button which is below the current page. business settings page. + You will see the system users under the section of Users on the left sidebar. After you have created your new user click on _Add Assets_ and choose _App>Select App Name>Full control option_ and save changes. Referencing attachment files inline for more details. | -| `disposition` | An optional disposition for the attachment (`inline` or `attachment`). Currently only supported for [SendGrid](/integrations/email/sendgrid) and [Postmark](/integrations/email/postmark). | +| Property | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name`\* | The name of the file | +| `content_type`\* | A mime type for the file | +| `content`\* | The base64-encoded file content. Attachments count toward the 10MB trigger payload limit, which applies to the full request. Note that some email providers may enforce stricter limits. | +| `content_id` | An optional unique `Content-ID` for the attachment. Currently only supported for SendGrid, Postmark, and Resend. See Referencing attachment files inline for more details. | +| `disposition` | An optional disposition for the attachment (`inline` or `attachment`). Currently only supported for SendGrid and Postmark. |
    ```js title="An example attachment object" @@ -32,19 +32,15 @@ Every attachment you send to Knock in your `data` payload should include the fol } ``` -**Note**: each attachment object must be less than 10mb, but you should confirm whether your email provider has a lower limit. - ## Sending attachments in your trigger call -Once you've specified your attachment key in your email template, the last step is to send the data along to Knock. Remember that you need to base64 encode the contents of the file. +Once you've specified your attachment key in your email template, include the base64-encoded attachment data in the `data` payload of your trigger call. To send multiple attachments, pass an array of attachment objects under the attachment key. -**Note**: you can send multiple attachments for an email by including an array of attachment objects under the attachment key in your data payload. - ## Sending a different attachment per recipient If you need to send a different attachment per recipient in a workflow then you'll need to make one trigger call per recipient, such that the data payload is unique to that recipient. @@ -74,10 +70,23 @@ filesAndRecipients.forEach(({ id, file }) => { ## Referencing attachment files inline -If you're using [SendGrid](/integrations/email/sendgrid) or [Postmark](/integrations/email/postmark) as your email provider, you can set attachment files to be inline in your email template by providing a `disposition` property of `inline` on the attachment object you send in your trigger call. +If you're using [SendGrid](/integrations/email/sendgrid) or [Postmark](/integrations/email/postmark) as your email provider, you can set attachment files to be inline in your email messages by providing a `disposition` property of `inline` on the attachment object you send in your trigger call. + +[Resend](/integrations/email/resend) supports inline images (see below), but does not allow you to explicitly set the disposition of an attachment. ### Inline image attachments + + When using Resend, you can reference images inline without setting a{" "} + disposition property on the attachment object. + + } +/> + To attach image files to your email and reference them inline, add a `content_id` and `disposition` to the attachment object. You can then reference the attachment in your template using the `cid:` prefix and the `content_id` value. ```js title="An example inline image attachment" diff --git a/content/integrations/email/aws-ses.mdx b/content/integrations/email/aws-ses.mdx index c55238165..3d5da8dc2 100644 --- a/content/integrations/email/aws-ses.mdx +++ b/content/integrations/email/aws-ses.mdx @@ -97,7 +97,7 @@ You can create a new Amazon SES channel in the dashboard under **Channels and so Here are a few other things to keep in mind once you have your SES channel configured in Knock: -- **SES sandbox mode.** By default, AWS places all new accounts in the SES sandbox. While your account is in the sandbox, you can only send emails to verified email addressβ€”keep this in mind if you're testing in development before you've moved your account out of the SES sandbox. For more information on the SES sandbox and how to move your account out of it, see the [SES sandbox documentation](https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html). +- **SES sandbox mode.** By default, AWS places all new accounts in the SES sandbox. While your account is in the sandbox, you can only send emails to verified email addressβ€”keep this in mind if you're testing in development before you've moved your account out of the SES sandbox. For more information on the SES sandbox and how to move your account out of it, see the SES sandbox documentation. - **Deliverability tracking.** By default, SES channels do not track delivery beyond "Sent". However, you can enable [delivery status webhooks](#delivery-status-webhooks) to receive real-time updates about email delivery and bounces. ## Delivery status webhooks diff --git a/content/integrations/email/client-previews.mdx b/content/integrations/email/client-previews.mdx index 3bb711c6f..5efad1f2e 100644 --- a/content/integrations/email/client-previews.mdx +++ b/content/integrations/email/client-previews.mdx @@ -12,7 +12,10 @@ layout: integrations text={ <> Email client previews are currently only available on our{" "} - Enterprise plan. + + Enterprise plan + + . } /> diff --git a/content/integrations/email/cloudflare-email.mdx b/content/integrations/email/cloudflare-email.mdx new file mode 100644 index 000000000..93f8d3ae3 --- /dev/null +++ b/content/integrations/email/cloudflare-email.mdx @@ -0,0 +1,149 @@ +--- +title: How to send email with Cloudflare Email +description: How to send transactional email notifications with Cloudflare Email Service and Knock. +tags: ["cloudflare", "email", "email service", "transactional"] +section: Integrations > Email +layout: integrations +--- + +Knock supports using Cloudflare Email Service to send email notifications to your users. + + + Cloudflare Email Service requires your domain's DNS to be configured + correctly on Cloudflare. See{" "} + + Email Service domain configuration + {" "} + for DNS record details. + + } +/> + + + Cloudflare Email Service does not currently support delivery status + tracking. Knock will not receive bounce or delivery confirmation events + from Cloudflare for messages sent through this channel. + + } +/> + +## Features + +- Knock link and open tracking +- Per environment configuration +- Sandbox mode + +## Getting started + +1. In the Cloudflare dashboard, create an **API token** (or use a compatible **API key**) with permission to send email for your account, and copy your Cloudflare **account ID**. See Cloudflare's API token documentation and how to find your account ID. +2. In Knock, open **Channels and sources** in your account settings and create a **Cloudflare Email** channel. +3. For each [environment](/concepts/environments), open **Manage configuration** on the channel and enter your **Account ID** and **API key** (your Cloudflare API token or key), plus your default **From** address and optional **From** name. Use addresses on domains you have verified for sending in Cloudflare. + +## Channel configuration + +The following channel settings should be configured per [environment](/concepts/environments). Navigate to **Channels and sources** in your dashboard account settings, select your Cloudflare Email [channel](/concepts/channels), then click "Manage configuration" under the environment that you'd like to configure. + + + + Fields marked with an `*` are required. + + **Knock settings** + + + + + + + **Provider settings for Cloudflare Email** + + + + + + + + + + When configured, these optional overrides will apply to all emails sent from this channel in the configured environment. See [Settings and overrides](/integrations/email/settings) for more on email channel overrides. + + + + + + + + + + + + Set optional per-environment [conditions](/integrations/overview#channel-conditions) for this channel. These conditions are evaluated each time a workflow run encounters a step that uses this channel in the configured environment. If the conditions are not met, the step will be skipped. + + + +## Recipient data requirements + +To send an email notification you'll need a valid `email` property set on your recipient. diff --git a/content/integrations/email/layouts.mdx b/content/integrations/email/layouts.mdx index 6a909c296..b320e00df 100644 --- a/content/integrations/email/layouts.mdx +++ b/content/integrations/email/layouts.mdx @@ -17,13 +17,13 @@ tags: ] --- -When you use Knock to power your email notifications, you use two main concepts to build the notifications that will be sent to your users: layouts and templates. +Knock emails are built from two pieces: layouts and templates. -The **layout** typically includes the header and footer of your email, as well as any other HTML or CSS that will be used across all (or multiple) templates. You can think of your email layout as the "frame" of your email notifications, where you define the shared structure and styles once for all your email notifications so they can look and feel consistently without having to repeat them in every template. +The **layout** is the shared "frame" of your email. The header, footer, and any HTML or CSS you want applied across templates. You define it once so every email renders consistently. -The **template** is the actual body and content of your email. When you add an email step to a workflow, the content that you edit within the template editor is the template that will be wrapped by the layout mentioned above. Under the hood, Knock is injecting this template into the `{{content}}` variable of the email layout. +The **template** is the body of a specific email, authored in an email workflow step. At send time, Knock injects the template into the `{{content}}` variable of its layout to produce the final email. -Here's an example of a transactional email we send at Knock, complete with the template content merged into the layout. The area shaded in green is the template. The area shaded in blue is the layout. +Here's an example of a transactional email we send at Knock, with the template content merged into the layout. Green is the template, blue is the layout. -Here's a quick overview of the system-level variables Knock provides for use in your layouts. +Knock provides two system-level variables for use in your layouts: -| Variable | Description | -| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `content` | A notification template defined in an email channel step will be inserted into this variable. **This is a required variable, and must be present somewhere in every layout.** | -| `footer_links` | A list of footer links, as configured in the layout editor, will be injected into this variable. This is an optional variable and is not required in custom layouts. | +| Variable | Description | +| -------------- | ---------------------------------------------------------------------------------------------------------------- | +| `content` | The rendered email template is injected here. **Required β€” must appear somewhere in every layout.** | +| `footer_links` | The footer links configured in the layout editor are injected here. Optional and not required in custom layouts. | ## The Knock default layout -Your Knock account starts with a pre-built default layout. If you navigate to **Content** > **Email layouts** in the sidebar of your Knock dashboard, you'll find this default layout. This default layout will be used by all new email templates when they're initially created, so if you want to change the default layout used by your emails, this is the one to update. +Every Knock account starts with a default layout at **Content** > **Email layouts**. New email templates use this layout by default, so update it to change the shared look and feel of all your emails. -Click on the "Default" layout in your layouts list to enter the layout editor. You'll start by looking at the pre-built layout that Knock gives you out of the box. You'll find a footer design option in this visual builder where you can add footer links to this pre-built layout for HTML emails. You can update the logo, icon, and brand color of your email layout by going to the **Branding** page under the **Account** section of your account settings and updating these attributes. +Click the "Default" layout to open the visual editor, where you can configure footer links for HTML emails. Logo, icon, and brand color come from the **Branding** page under your account settings. To edit the HTML and CSS directly, click "Edit in code editor" to open the [custom layout](/integrations/email/layouts#custom-layouts-and-styling) editor. -If you'd rather create your own layout from scratch, you can click "Edit in code editor" in the top right corner of the layout editor to enter our [custom layout](/integrations/email/layouts#custom-layouts-and-styling) editor. +Each layout also includes a plaintext version under the "Text" tab. It works the same way, the plaintext body of your template is injected into `{{content}}`. -Every layout includes the text layout for plaintext emails, and the concept works the same as the HTML layout where the text content of your email template will be injected into the `{{content}}` variable. Click the "Text" tab to switch to the text layout, and edit it as you see fit. +## Select a layout for your email template -## Selecting a layout for a given email template - -All new email templates created within workflows will use your `default` email layout. If you would like to use a different layout for a specific step, open the template editor, click the gear icon (βš™οΈ) to access the template settings, and select your preferred layout from the "Email layout" dropdown in the modal. After saving, you can use the preview pane on the right side of the editor to see how your template will render with the selected layout. - -If you'd like to create an email step in your workflow that contains the full HTML document at the template level (without a layout wrapper), you can select "No layout" in this dropdown. +New email templates use your `default` layout. To use a different layout on a specific step, open the template editor and pick one from the "Email layout" dropdown. - Email layouts follow the Knock environment commit model, so you'll need to - commit them to your current environment before you'll see them appear in - your email notifications. + Email layouts follow the Knock environment commit model β€” commit them to + your current environment before they'll appear in your email + notifications. } /> ## Custom layouts and styling -If you want to create your own custom email layouts, you can go into the layout editor and click "Edit in code editor" to go to an HTML and CSS editor for your email layout. The important thing to remember here is that your layout needs a `{{content}}` somewhere in its `body` tag for the email template to be injected into the layout. +To create a custom layout, open the layout editor and click "Edit in code editor" to edit HTML and CSS directly. Your layout must include `{{content}}` somewhere inside its `` as that's where Knock injects the template at send time. + +Layouts can also use [MJML](/integrations/email/mjml). An MJML layout must have a root `` tag instead of an HTML document, and any plain HTML inside must be wrapped in `` tags. - if you're providing a custom HTML layout for your emails, the layout must - be a valid HTML document. + If you're providing a custom HTML layout for your emails, the layout must + be a valid HTML document. For MJML layouts, the layout must be a valid + MJML document with the <mjml> root tag. } /> ### Creating new layouts -To create a new layout, go to **Developers** > **Layouts** and click "Create layout." All new layouts start in Knock's visual layout editor, but you can override this default by clicking "Edit in code editor." +To create a new layout, go to **Developers** > **Layouts** and click "Create layout." New layouts open in the visual editor by default; click "Edit in code editor" to switch to HTML and CSS. ### Using custom fonts -Knock supports custom web fonts that are referenced inside your email layout's ``. This should be done via a `` tag, as the style `@import` rule is not supported: +Reference custom web fonts inside your layout's `` with a `` tag. The `@import` rule is not supported: ```html ``` -Because many widely-used email clients do not support web fonts, we highly recommend choosing a web safe font as a fallback when using custom web fonts to ensure a consistent user experience. +Many email clients don't support web fonts, so pair any custom font with a web-safe fallback. ### Using variables and brand attributes in a custom layout -It's helpful to remember that you can use variables you create at the account and environment-levels by injecting them into your layout with the `vars.*` namespace. This is a great tool for global values that will be the same across all emails you send, such as base URL for embedded links in your email notifications. +Inject account- and environment-level variables into your layout with the `vars.*` namespace. This is useful for global values that are the same across all emails, like a base URL for embedded links. -You can also use the `vars.branding.*` namespace for injecting the branding properties you set in account settings. The following branding properties are available for use in custom layouts. +Branding properties set in account settings are available under `vars.branding.*`: - `vars.branding.logo_url` - `vars.branding.icon_url` - `vars.branding.primary_color` - `vars.branding.primary_color_contrast` -If you use [per-tenant branding](/concepts/tenants#custom-branding), remember that Knock automatically evaluates these properties and shows the relevant branding elements based on the `tenant_id` associated with a given workflow run. As an example: if I trigger a workflow that includes a tenant_id with custom branding elements set on their `tenant`, the layout will use those branding elements. If no custom branding elements are set on the `tenant`, then the account default branding elements will be used. +With [per-tenant branding](/multi-tenancy/per-tenant-branding), Knock resolves these properties against the `tenant_id` on the workflow run, falling back to account-level branding if the tenant has none set. -### Injecting workflow run scope into a layout at runtime +### Dark mode support -If you have a workflow-run-scoped variable that you'd like to inject into your layouts, you can do so with normal liquid variable injection, so long as the variables come after the place in your layout where `{{content}}` is first rendered. (If you need to inject variables into your layout above your `{{content}}` variable, see the ["Defining pre-content variables" section](/integrations/email/layouts#defining-pre-content-variables) below.) +The Knock default email layout supports dark mode out of the box via the `prefers-color-scheme` CSS media query. -As an example, let's say my email template is using the [Liquid capture tag](https://shopify.github.io/liquid/tags/variable/) to create a variable called `formatted_price` within the email template. If I want to inject that `formatted_price` value into the footer of my layout, I can do so by using the `{{formatted_price}}` variable in the footer of my layout file. + + If your default layout was created before April 21, 2026, it does not + support dark mode. Either create a new layout (which starts from Knock's + latest default) or add dark mode support to your existing layout using the + snippet below. + + } +/> -### Defining pre-content variables +To opt a custom layout in to dark mode, add color scheme meta tags, set `color-scheme` on `:root`, and override colors inside a `@media (prefers-color-scheme: dark)` block: + +```html title="Adding dark mode support to a custom layout" + + + + +``` + +#### Swapping logos and icons in dark mode + +If your logo or icon doesn't render well on a dark background, upload dark-mode versions in account branding and expose them via `vars.branding.dark_logo_url` and `vars.branding.dark_icon_url`. Render both images in your layout and toggle them with CSS: + +```html title="Rendering light and dark logos" +{% assign dark_logo_url = vars.branding.dark_logo_url | default: +vars.branding.logo_url %} + + + {{ vars.app_name }} + + {{ vars.app_name }} + + +``` + +Hide the dark image by default and swap them inside your dark mode media query: + +```css title="CSS to toggle light and dark images" +.dark-img { + display: none; + visibility: hidden; +} -When the Knock notification engine sends an email notification, it compiles your email template with its selected layout to create a **single email template** that is sent to your recipient. +@media (prefers-color-scheme: dark) { + .dark-img { + display: inline !important; + visibility: visible !important; + } -In this process, the templates that you define in your email channel steps are injected into the `{{ content }}` tag in your email layout. + .light-img { + display: none !important; + visibility: hidden !important; + } +} +``` -In cases where you want to inject a template-level variable above the `{{ content }}` tag of your email layout, you can set **pre-content variables** to inject variables into your layout above where your `{{ content }}` tag will render. +The ` + + + + +``` + + + + +The `useGuide` composable mirrors the React [`useGuide`](/in-app-ui/react/sdk/hooks/use-guide) hook β€” it fetches a single guide by `type` or `key`. It takes the `knockGuides` instance as a parameter, which the calling component retrieves via `inject` from `KnockProvider`. + +```typescript title="composables/useGuide.ts" +import { computed } from "vue"; + +interface GuideFilters { + type?: string; + key?: string; +} + +export function useGuide( + knockGuides: ReturnType< + typeof import("./useKnockGuideClient").useKnockGuideClient + > | null, + options: GuideFilters = {}, +) { + const { type, key } = options; + + if (!knockGuides) { + console.warn("knockGuides instance is required for guide functionality"); + return { + step: computed(() => null), + guides: computed(() => []), + loading: computed(() => false), + error: computed(() => null), + isReady: computed(() => false), + }; + } + + // Filter from the reactive guides ref so step recomputes whenever the + // store subscription updates state.guides in useKnockGuideClient + const step = computed(() => { + const match = knockGuides.guides.value.find((g) => { + if (type && g.type !== type) return false; + if (key && g.key !== key) return false; + return true; + }); + + // getStep() applies the SDK's display-ordering logic rather than + // indexing steps directly + return match?.getStep() ?? null; + }); + + return { + step, + guides: knockGuides.guides, + loading: knockGuides.loading, + error: knockGuides.error, + isReady: knockGuides.isReady, + }; +} +``` + +Each guide step exposes a `content` object whose shape is determined by your [message type schema](/in-app-ui/message-types#schemas). Common properties include: + +| Property | Type | Description | +| ------------------ | ---------------------------------- | ----------------------------------- | +| `title` | `string` | Guide title text. | +| `body` | `string` | Guide body content (supports HTML). | +| `primary_button` | `{ text: string; action: string }` | Primary action button. | +| `secondary_button` | `{ text: string; action: string }` | Secondary action button. | +| `dismissible` | `boolean` | Whether the guide can be dismissed. | + +Each step also provides these methods for [tracking engagement](/in-app-ui/guides/handling-engagement): + +| Method | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------- | +| `markAsSeen()` | Marks the guide as seen. | +| `markAsInteracted()` | Marks the guide as interacted, recording a click event or other user interaction, with optional metadata. | +| `markAsArchived()` | Marks the guide as archived, removing the guide from eligibility for the user. | + + + + +With the composables in place, you can build components to render guides. This example shows a banner component that renders a guide, tracks engagement, and handles dismissal. + +```vue title="components/GuideBanner.vue" + + + +``` + + + + +The implementation above authenticates users by `id` only. For production use, you should also pass a [user token](/in-app-ui/security-and-authentication) β€” a short-lived JWT signed by your backend β€” to verify that the client is authorized to act on behalf of the user. + + + + +## Troubleshooting + +See [debugging guides](/in-app-ui/guides/debugging-guides) for general troubleshooting guidance related to guide activation and targeting. You may also encounter these Vue-specific issues: + +| Issue | Solution | +| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Guides are not appearing after authentication. | Check that `knock.knockClient.value` exists before calling `initializeGuideClient`. Ensure `authenticate` is called before `initializeGuideClient` in `onMounted`. | +| The store subscription is not updating components. | Confirm that `guideClient.value.store.subscribe` is being called inside `initializeGuideClient`, before `guideClient.value.subscribe()`. | +| [Guide activation rules](/in-app-ui/guides/create-guides#activation) aren't working. | Ensure `urlpattern-polyfill` is imported at the top of your app entry point before initializing Knock: `import "urlpattern-polyfill"` | + +## Related docs + +- [Knock guides overview](/in-app-ui/guides/overview) +- [Knock JavaScript SDK reference](/in-app-ui/javascript/sdk/reference) +- [Message types and schemas](/in-app-ui/message-types) diff --git a/content/tutorials/implementation-guide.mdx b/content/tutorials/implementation-guide.mdx index 8a3558da8..2c18be0fa 100644 --- a/content/tutorials/implementation-guide.mdx +++ b/content/tutorials/implementation-guide.mdx @@ -136,7 +136,7 @@ While there is no one-size-fits-all approach to planning a migration to Knock, w - If your notifications should be scoped to a particular workspace or organization, you’ll need to implement [Tenants](/concepts/tenants) in your Knock integration. A `tenant` can be applied as context to a workflow trigger in order to [apply per-tenant branding](/concepts/tenants#custom-branding), [per-tenant preferences](/concepts/tenants#per-tenant-user-preferences-and-tenant-preference-defaults), and [scope in-app feed messages to particular tenants](/concepts/tenants#scoping-in-app-feeds). + If your notifications should be scoped to a particular workspace or organization, you’ll need to implement [Tenants](/multi-tenancy/overview) in your Knock integration. A `tenant` can be applied as context to a workflow trigger in order to [apply per-tenant branding](/multi-tenancy/per-tenant-branding), [per-tenant preferences](/multi-tenancy/per-tenant-preferences), and [scope in-app feed messages to particular tenants](/multi-tenancy/tenant-scoping). Once your recipients (both users and objects) and tenants have been migrated to Knock, you’ll want to consider delivery preferences for your notifications to give your users control of where and when they receive updates from your product. For more information on building a preference center in your app, check out the section on [Completing your client-side integration](#completing-your-client-side-integration) below. - Knock’s powerful [Preferences](/preferences/overview) API allows your users to opt out of notifications based on the notification’s delivery `channel_type`, the `category` of the notification, the specific notification `workflow`, or a combination of these properties. You can also extend these preferences to be [tenant-specific](/preferences/tenant-preferences) or to [evaluate conditionally](/preferences/preference-conditions) at runtime. + Knock’s powerful [Preferences](/preferences/overview) API allows your users to opt out of notifications based on the notification’s delivery `channel_type`, the `category` of the notification, the specific notification `workflow`, or a combination of these properties. You can also extend these preferences to be [tenant-specific](/multi-tenancy/per-tenant-preferences) or to [evaluate conditionally](/preferences/preference-conditions) at runtime. You can set environment-level default preferences (for example, maybe a given workflow should require a user to manually opt in to receive those notifications) as well as tenant-specific default preferences that will be overridden by a recipient’s individual preferences. diff --git a/content/tutorials/migrate-email-with-mcp-server.mdx b/content/tutorials/migrate-email-with-mcp-server.mdx index 4d2cebbc7..6e3dae9b2 100644 --- a/content/tutorials/migrate-email-with-mcp-server.mdx +++ b/content/tutorials/migrate-email-with-mcp-server.mdx @@ -15,7 +15,7 @@ tags: section: Tutorials --- -Storing your email templates in Knock comes with [many advantages](/template-editor/overview#frequently-asked-questions). In this tutorial, we'll walk you through how to leverage Knock's [MCP server](/developer-tools/mcp-server) with the large language model (LLM) of your choice as a low-effort solution to quickly migrate your email templates to Knock. +Storing your email templates in Knock comes with [many advantages](/template-editor/overview#frequently-asked-questions). In this tutorial, we'll walk you through how to leverage Knock's [MCP server](/ai/mcp-server) with the large language model (LLM) of your choice as a low-effort solution to quickly migrate your email templates to Knock. ## Prerequisites @@ -23,82 +23,22 @@ Before getting started, you'll need access to the following: - A [Knock account](https://dashboard.knock.app/). - A configured [email provider](/integrations/email/overview) in your Knock account. Navigate to **Integrations** > **Channels** in your dashboard to set this up if you haven't already done so. -- A [service token](https://docs.knock.app/developer-tools/service-tokens) for your Knock account. -- An MCP-compatible client (such as Cursor or Claude Desktop). We recommend Cursor for working with a large collection of HTML files. -- For the purposes of this tutorial, we will assume that you're familiar with prompting an agentic LLM and understand how to provide access to files as context for your prompts. If you're not sure what this means, we recommend checking out this guide on working with context from Cursor. -- Your existing email template files. Your templates will be upserted to Knock as HTML, so if you're using a format like MJML or React Email, you'll want to convert them to HTML before you begin. - -For more information on setting up the Knock MCP server, see the [Get started](/developer-tools/mcp-server#get-started) section of the MCP documentation. - -### Required MCP tools - -When working with MCP servers, it's recommended that you expose only the tools you need for your use case to your LLM. The steps in this tutorial will require access to the following tools: - -- `documentation.*` -- `channels.*` -- `commits.*` -- `emailLayouts.*` -- `partials.*` -- `workflows.*` -- `users.*` - -Learn more about configuring these tools in your MCP-compatible client [here](/developer-tools/mcp-server#configuring-the-available-tools). If you're using Cursor or Claude Desktop, you can simply copy and paste the relevant configuration below into your MCP settings, under the `mcpServers` key. Be sure to update the configuration with your own service token. - - - - ```json title="Cursor MCP server configuration for email migration" - { - "knock": { - "command": "npx", - "args": [ - "-y", - "@knocklabs/agent-toolkit", - "-p", - "local-mcp", - "--tools", - "documentation.*", - "channels.*", - "commits.*", - "emailLayouts.*", - "partials.*", - "workflows.*", - "users.*" - ], - "name": "Knock MCP Server", - "env": { - "KNOCK_SERVICE_TOKEN": "YOUR-SERVICE-TOKEN" - } - } - } - ``` - - - ```json title="Claude Desktop MCP server configuration for email migration" - { - "knock": { - "command": "npx", - "args": [ - "-y", - "@knocklabs/agent-toolkit", - "-p", - "local-mcp", - "--tools", - "documentation.*", - "channels.*", - "commits.*", - "emailLayouts.*", - "partials.*", - "workflows.*", - "users.*" - ], - "env": { - "KNOCK_SERVICE_TOKEN": "YOUR-SERVICE-TOKEN" - } - } - } - ``` - - +- An MCP-compatible client (such as Cursor, Claude Code, or Claude Desktop). We recommend Cursor for working with a large collection of HTML files. +- Your existing email template files. Your templates will be upserted to Knock as HTML, so if you're using React Email or another format, you'll want to convert them to HTML before you begin. Knock supports [MJML](/integrations/email/mjml) natively, so MJML templates do not need to be converted. + +For the purposes of this tutorial, we will assume that you're familiar with prompting an agentic LLM and understand how to provide access to files as context for your prompts. If you're not sure what this means, we recommend checking out this guide on working with context from Cursor. For more information on setting up the Knock MCP server, see the [Get started](/ai/mcp-server#get-started) section of the MCP documentation. + +### MCP permissions + +You'll need to enable the following permissions to complete the steps in this tutorial. + +| Permission | Requirement | Purpose | +| ---------------- | ----------- | ------------------------------------------------------------------ | +| Manage resources | Required | Create partials, email layouts, and workflows with email templates | +| Commits | Required | Commit your changes to your development environment | +| Debug | Optional | View environment logs and inspect environments while testing | +| Manage data | Optional | Create test users | +| Documentation | Required | Search Knock documentation | ## Background @@ -120,7 +60,7 @@ While not required reading, you may want to familiarize yourself with the follow
    • - We recommend leveraging an LLM with the largest context window + We recommend leveraging a model with the largest context window available. In Cursor, you'll likely have the best results if you enable "MAX mode" on your agent. Note that this may have billing implications depending on your Cursor plan. @@ -168,7 +108,7 @@ Now that you have everything you need for your migration, you're ready to get st Each step below contains a prompt that you can provide to your LLM. These prompts are designed to be run one at a time in order to provide the LLM with discrete tasks, thus improving the accuracy of the output. - + Before creating any resources in Knock, you'll need to analyze your existing email templates to extract layouts and shared components. Copy the prompt below and provide it to your LLM along with access to your template files. We recommend limiting your prompt to 5-10 template files at a time to avoid exceeding the context window limit. Repeat this prompt as many times as necessary until you've analyzed all of your templates. @@ -286,7 +226,7 @@ Each step below contains a prompt that you can provide to your LLM. These prompt ``` - + After creating all resources, you need to verify that the migration was successful. We'll ask our LLM to provide a summary report of the migration to ensure that nothing was missed. Provide the prompt below to your LLM along with access to the `templates.md` file. @@ -315,7 +255,7 @@ Each step below contains a prompt that you can provide to your LLM. These prompt ``` - + Once you've summarized the migration and verified that you've created all of the resources identified, you can use the Knock MCP server to test the migrated templates. Make sure that you update the prompt to target the correct test user's ID. diff --git a/content/tutorials/migrate-from-braze.mdx b/content/tutorials/migrate-from-braze.mdx index 92f6ca080..14ba8ea43 100644 --- a/content/tutorials/migrate-from-braze.mdx +++ b/content/tutorials/migrate-from-braze.mdx @@ -51,7 +51,7 @@ When using the `t` tag [method](/template-editor/translations#translation-method In Braze, there isn't a direct equivalent to Knock's tenant functionality. Most Braze customers handle customer/organization segmentation through custom attributes (like `organization_id`, `account_id`, `company_name`) combined with segments built on those attributes. This approach requires manual campaign targeting, complex segmentation logic, and offers no native support for per-organization branding, preferences, or scoped in-app feeds. -In Knock, [tenants](/concepts/tenants) provide native multi-tenancy support that eliminates these workarounds. Tenants represent organizations your users belong toβ€”what you might call "accounts" or "workspaces." Per-tenant branding attributes are stored directly on a tenant in Knock rather than requiring separate campaigns or complex attribute management. Knock tenants are applied as context to workflow triggers to automatically [apply per-tenant branding](/concepts/tenants#custom-branding), [manage per-tenant preferences](/preferences/tenant-preferences), and [scope in-app feed messages to particular tenants](/concepts/tenants#scoping-in-app-feeds). +In Knock, [tenants](/multi-tenancy/overview) provide native multi-tenancy support that eliminates these workarounds. Tenants represent organizations your users belong toβ€”what you might call "accounts" or "workspaces." Per-tenant branding attributes are stored directly on a tenant in Knock rather than requiring separate campaigns or complex attribute management. Knock tenants are applied as context to workflow triggers to automatically [apply per-tenant branding](/multi-tenancy/per-tenant-branding), [manage per-tenant preferences](/multi-tenancy/per-tenant-preferences), and [scope in-app feed messages to particular tenants](/multi-tenancy/tenant-scoping). Key advantages of Knock's tenant approach: @@ -83,7 +83,7 @@ Braze manages user subscription preferences through Tenants in Courier to scope your notifications to a particular workspace or organization (and optionally associating Brands with those Tenants), you can achieve similar functionality with Knock [Tenants](/concepts/tenants). +If you’re currently using Tenants in Courier to scope your notifications to a particular workspace or organization (and optionally associating Brands with those Tenants), you can achieve similar functionality with Knock [Tenants](/multi-tenancy/overview). -Unlike Courier, per-tenant branding attributes are stored directly on a Tenant in Knock rather than as a separate resource. Knock also does not directly associate Tenants with the recipients of a notification (no subscription logic necessary!); rather, a `tenant` is applied as context to a particular workflow trigger in order to [apply per-tenant branding](/concepts/tenants#custom-branding), [per-tenant preferences](/concepts/tenants#per-tenant-user-preferences-and-tenant-preference-defaults), and [scope in-app feed messages to particular tenants](/concepts/tenants#scoping-in-app-feeds). +Unlike Courier, per-tenant branding attributes are stored directly on a Tenant in Knock rather than as a separate resource. Knock also does not directly associate Tenants with the recipients of a notification (no subscription logic necessary!); rather, a `tenant` is applied as context to a particular workflow trigger in order to [apply per-tenant branding](/multi-tenancy/per-tenant-branding), [per-tenant preferences](/multi-tenancy/per-tenant-preferences), and [scope in-app feed messages to particular tenants](/multi-tenancy/tenant-scoping). diff --git a/content/version-control/branches.mdx b/content/version-control/branches.mdx index a95ba10a7..f5d97508e 100644 --- a/content/version-control/branches.mdx +++ b/content/version-control/branches.mdx @@ -55,6 +55,8 @@ Optionally, you can also pass in the `--branch` flag to specify the branch you w knock workflow push my-workflow --branch my-branch --commit ``` +If you need to overwrite existing content in Knock (for example, when local changes should replace what is currently stored), you can add the `--force` flag to the push command. + ## Merging changes from a branch **In the dashboard** diff --git a/data/sidebars/apiOverviewSidebar.ts b/data/sidebars/apiOverviewSidebar.ts index cd2d3d1a6..d94baab27 100644 --- a/data/sidebars/apiOverviewSidebar.ts +++ b/data/sidebars/apiOverviewSidebar.ts @@ -2,6 +2,7 @@ import { SidebarSection } from "../types"; export const RESOURCE_ORDER = [ "workflows", + "workflow_recipient_runs", "messages", "channels", "users", diff --git a/data/sidebars/cliSidebar.ts b/data/sidebars/cliSidebar.ts index 708f70d99..3c3ac7273 100644 --- a/data/sidebars/cliSidebar.ts +++ b/data/sidebars/cliSidebar.ts @@ -169,4 +169,18 @@ export const CLI_SIDEBAR: SidebarContent[] = [ { slug: "/validate", title: "Validate message types" }, ], }, + { + title: "Audiences", + slug: "/cli/audience", + pages: [ + { slug: "/", title: "Overview" }, + { slug: "/file-structure", title: "File structure" }, + { slug: "/list", title: "List audiences" }, + { slug: "/get", title: "Get audiences" }, + { slug: "/pull", title: "Pull audiences" }, + { slug: "/push", title: "Push audiences" }, + { slug: "/validate", title: "Validate audiences" }, + { slug: "/archive", title: "Archive audiences" }, + ], + }, ]; diff --git a/data/sidebars/developerToolsSidebar.ts b/data/sidebars/developerToolsSidebar.ts index a469e345b..b16ecf05f 100644 --- a/data/sidebars/developerToolsSidebar.ts +++ b/data/sidebars/developerToolsSidebar.ts @@ -70,11 +70,6 @@ export const DEVELOPER_TOOLS_SIDEBAR_CONTENT: SidebarContent[] = [ }, ], }, - { - slug: `${baseSlug}/mcp-server`, - title: "MCP server", - isBeta: true, - }, { slug: `${baseSlug}/building-with-llms`, title: "Building with LLMs", diff --git a/data/sidebars/integrationsSidebar.ts b/data/sidebars/integrationsSidebar.ts index a1e6b8b65..5fcd759ce 100644 --- a/data/sidebars/integrationsSidebar.ts +++ b/data/sidebars/integrationsSidebar.ts @@ -15,6 +15,11 @@ export const INTEGRATIONS_SIDEBAR: SidebarContent[] = [ slug: "/integrations/sources", pages: [ { slug: "/overview", title: "Overview" }, + { slug: "/clerk", title: "Clerk" }, + { slug: "/posthog", title: "PostHog" }, + { slug: "/stripe", title: "Stripe" }, + { slug: "/supabase", title: "Supabase" }, + { slug: "/workos", title: "WorkOS" }, { slug: "/segment", title: "Segment" }, { slug: "/rudderstack", title: "RudderStack" }, { slug: "/hightouch", title: "Hightouch" }, @@ -22,7 +27,16 @@ export const INTEGRATIONS_SIDEBAR: SidebarContent[] = [ { slug: "/polytomic", title: "Polytomic" }, { slug: "/jitsu", title: "Jitsu" }, { slug: "/freshpaint", title: "Freshpaint" }, - { slug: "/http", title: "HTTP" }, + { slug: "/custom", title: "Custom source" }, + { + title: "Legacy", + slug: "/legacy", + pages: [ + { slug: "/segment", title: "Segment", isLegacy: true }, + { slug: "/rudderstack", title: "RudderStack", isLegacy: true }, + { slug: "/http", title: "HTTP", isLegacy: true }, + ], + }, ], }, { @@ -31,11 +45,13 @@ export const INTEGRATIONS_SIDEBAR: SidebarContent[] = [ pages: [ { slug: "/overview", title: "Overview" }, { slug: "/layouts", title: "Layouts" }, + { slug: "/mjml", title: "MJML" }, { slug: "/settings", title: "Settings and overrides" }, { slug: "/attachments", title: "Sending attachments" }, { slug: "/client-previews", title: "Client previews" }, + { slug: "/knock-test", title: "Knock test emails" }, { slug: "/aws-ses", title: "Amazon SES" }, - { slug: "/knock-test", title: "Knock (test)" }, + { slug: "/cloudflare-email", title: "Cloudflare Email" }, { slug: "/mailersend", title: "MailerSend" }, { slug: "/mailgun", title: "Mailgun" }, { slug: "/mailjet", title: "Mailjet" }, diff --git a/data/sidebars/mapiOverviewSidebar.ts b/data/sidebars/mapiOverviewSidebar.ts index a2f67091a..b40bf269e 100644 --- a/data/sidebars/mapiOverviewSidebar.ts +++ b/data/sidebars/mapiOverviewSidebar.ts @@ -8,6 +8,7 @@ export const RESOURCE_ORDER = [ "templates", "broadcasts", "email_layouts", + "audiences", "partials", "guides", "message_types", @@ -15,6 +16,7 @@ export const RESOURCE_ORDER = [ "translations", "variables", "branches", + "members", "api_keys", "auth", "$shared", diff --git a/data/sidebars/platformSidebar.ts b/data/sidebars/platformSidebar.ts index dfe38d193..b01d7dd86 100644 --- a/data/sidebars/platformSidebar.ts +++ b/data/sidebars/platformSidebar.ts @@ -33,7 +33,6 @@ export const PLATFORM_SIDEBAR: SidebarSection[] = [ { slug: "/recipients", title: "Recipients" }, { slug: "/users", title: "Users" }, { slug: "/preferences", title: "Preferences" }, - { slug: "/tenants", title: "Tenants" }, { slug: "/objects", title: "Objects" }, { slug: "/subscriptions", title: "Subscriptions" }, { slug: "/audiences", title: "Audiences" }, @@ -43,31 +42,67 @@ export const PLATFORM_SIDEBAR: SidebarSection[] = [ { slug: "/variables", title: "Variables" }, ], }, + { + title: "Knock AI", + slug: "/ai", + desc: "Use AI in the dashboard, inside workflows, in your IDE, and in external clients.", + pages: [ + { slug: "/overview", title: "Overview" }, + { slug: "/agent", title: "Knock agent" }, + { slug: "/agent-function", title: "Agent workflow function" }, + { slug: "/cli", title: "Knock CLI" }, + { slug: "/mcp-server", title: "Knock MCP server" }, + { slug: "/skills", title: "Skills" }, + ], + }, { title: "Workflows", slug: "/designing-workflows", desc: "Learn how to design notifications using Knock's workflow builder, then explore advanced features such as batching, delays, and more.", pages: [ { slug: "/overview", title: "Overview" }, - { slug: "/delay-function", title: "Delay function" }, - { slug: "/batch-function", title: "Batch function" }, - { slug: "/branch-function", title: "Branch function" }, - { - slug: "/experiment-function", - title: "Experiment function", - isBeta: true, - }, - { slug: "/fetch-function", title: "Fetch function" }, { - slug: "/ai-agent-function", - title: "AI agent function", - isBeta: true, + slug: "", + title: "Function steps", + pages: [ + { slug: "/delay-function", title: "Delay function" }, + { slug: "/batch-function", title: "Batch function" }, + { slug: "/branch-function", title: "Branch function" }, + { slug: "/experiment-function", title: "Experiment function" }, + { slug: "/fetch-function", title: "Fetch function" }, + { slug: "/ai-agent-function", title: "Agent function" }, + { slug: "/throttle-function", title: "Throttle function" }, + { + slug: "/trigger-workflow-function", + title: "Trigger workflow function", + }, + ], }, - { slug: "/throttle-function", title: "Throttle function" }, { - slug: "/trigger-workflow-function", - title: "Trigger workflow function", - isBeta: true, + slug: "", + title: "Data steps", + pages: [ + { + slug: "/update-user-function", + title: "Update user function", + }, + { + slug: "/update-tenant-function", + title: "Update tenant function", + }, + { + slug: "/update-object-function", + title: "Update object function", + }, + { + slug: "/update-data-function", + title: "Update data function", + }, + { + slug: "/update-audience-function", + title: "Update audience function", + }, + ], }, { slug: "/step-conditions", title: "Step conditions" }, { slug: "/channel-step", title: "Channel steps" }, @@ -139,13 +174,24 @@ export const PLATFORM_SIDEBAR: SidebarSection[] = [ ], }, + { + title: "Multi-tenancy", + slug: "/multi-tenancy", + desc: "Learn how to use Knock's multi-tenancy features to power per-tenant notification experiences.", + pages: [ + { slug: "/overview", title: "Overview" }, + { slug: "/tenant-scoping", title: "Tenant scoping" }, + { slug: "/per-tenant-branding", title: "Per-tenant branding" }, + { slug: "/per-tenant-translations", title: "Per-tenant translations" }, + { slug: "/per-tenant-preferences", title: "Per-tenant preferences" }, + ], + }, { title: "Preferences", slug: "/preferences", desc: "Learn how to power notification preferences with Knock.", pages: [ { slug: "/overview", title: "Overview" }, - { slug: "/tenant-preferences", title: "Tenant preferences" }, { slug: "/object-preferences", title: "Object preferences" }, { slug: "/preference-conditions", title: "Preferences conditions" }, { @@ -180,6 +226,7 @@ export const PLATFORM_SIDEBAR: SidebarSection[] = [ { slug: "/data-retention", title: "Data retention" }, { slug: "/custom-domains", title: "Custom domains" }, { slug: "/managing-assets", title: "Managing assets" }, + { slug: "/account-deletion", title: "Account deletion" }, ], }, ]; diff --git a/data/sidebars/tutorialsSidebar.ts b/data/sidebars/tutorialsSidebar.ts index 411922cd2..f99e11f53 100644 --- a/data/sidebars/tutorialsSidebar.ts +++ b/data/sidebars/tutorialsSidebar.ts @@ -52,4 +52,8 @@ export const TUTORIALS_SIDEBAR: SidebarContent[] = [ slug: `${baseSlug}/migrate-email-with-mcp-server`, title: "Email template migration", }, + { + slug: `${baseSlug}/guides-in-vue`, + title: "Guides in Vue.js", + }, ]; diff --git a/data/specs/api/customizations.yml b/data/specs/api/customizations.yml index f2e4c7e4c..e46a048ff 100644 --- a/data/specs/api/customizations.yml +++ b/data/specs/api/customizations.yml @@ -85,6 +85,10 @@ resources: For a push channel, this includes device-specific tokens that map the recipient to the device they use, as well as [device metadata](/integrations/push/device-metadata) for supported providers. For chat apps, such as Slack, this includes the access token used to send notifications to a customer's Slack channel. The shape of the `data` payload varies depending on the channel type; you can learn more about channel data schemas [here](/send-notifications/setting-channel-data#provider-data-requirements). + workflow_recipient_runs: + name: Workflow runs + description: |- + A workflow run represents an individual execution of a [workflow](/concepts/workflows) for a specific recipient. Use the workflow runs API to inspect the status and event history of a workflow run, including which steps were executed and any errors encountered. schedules: name: Schedules description: |- diff --git a/data/specs/api/openapi.yml b/data/specs/api/openapi.yml index 7e5b6c05b..53f541cc5 100644 --- a/data/specs/api/openapi.yml +++ b/data/specs/api/openapi.yml @@ -229,6 +229,46 @@ components: type: array x-struct: null x-validate: null + ListWorkflowRecipientRunsResponse: + description: A paginated list of workflow recipient runs. + example: + items: + - __typename: WorkflowRecipientRun + actor: user_456 + error_count: 0 + id: 550e8400-e29b-41d4-a716-446655440000 + inserted_at: "2025-01-01T00:00:00Z" + recipient: user_123 + status: completed + tenant: tenant_abc + trigger_source: + cancellation_key: comment-123-user-456 + type: api + updated_at: "2025-01-01T00:05:00Z" + workflow: comment-created + workflow_run_id: 660e8400-e29b-41d4-a716-446655440000 + page_info: + __typename: PageInfo + after: null + before: null + page_size: 25 + properties: + items: + description: A list of workflow recipient runs. + items: + $ref: '#/components/schemas/WorkflowRecipientRun' + type: array + x-struct: null + x-validate: null + page_info: + $ref: '#/components/schemas/PageInfo' + required: + - items + - page_info + title: ListWorkflowRecipientRunsResponse + type: object + x-struct: Elixir.SwitchboardWeb.V1.Specs.ListWorkflowRecipientRunsResponse + x-validate: null PreferenceSetWorkflowCategorySettingObject: description: The settings object for a workflow or category, where you can specify channel types or conditions. example: @@ -802,6 +842,7 @@ components: enum: - message.archived - message.bounced + - message.created - message.delivered - message.delivery_attempted - message.interacted @@ -1204,6 +1245,49 @@ components: type: object x-struct: Elixir.SwitchboardWeb.V1.Specs.DiscordChannelData x-validate: null + WorkflowRecipientRunTriggerSource: + description: Describes how the workflow was triggered. + example: + cancellation_key: comment-123-user-456 + type: api + properties: + audience_key: + description: The key of the audience that triggered the workflow. + nullable: true + type: string + x-struct: null + x-validate: null + cancellation_key: + description: The cancellation key provided when the workflow was triggered via the API. + nullable: true + type: string + x-struct: null + x-validate: null + schedule_id: + description: The ID of the schedule that triggered the workflow. + nullable: true + type: string + x-struct: null + x-validate: null + type: + description: The type of trigger source. One of `api`, `audience`, `schedule`, `broadcast`, `workflow_step`, `integration`, or `rehearsal`. + enum: + - api + - audience + - schedule + - broadcast + - workflow_step + - integration + - rehearsal + type: string + x-struct: null + x-validate: null + required: + - type + title: WorkflowRecipientRunTriggerSource + type: object + x-struct: Elixir.SwitchboardWeb.V1.Specs.WorkflowRecipientRunTriggerSource + x-validate: null BulkDeleteUsersRequest: description: A request to delete users in bulk. example: @@ -2073,6 +2157,92 @@ components: type: object x-struct: Elixir.SwitchboardWeb.V1.Specs.ScheduleRepeatRule x-validate: null + WorkflowRecipientRunEvent: + description: An event that occurred during a workflow recipient run. + example: + __typename: WorkflowRecipientRunEvent + attempt: 1 + data: + channel_type: email + message_id: 2FVHPWxRqNuXQ9krvNP5A6Z4qXe + event: message_enqueued + id: 2FVHPWxRqNuXQ9krvNP5A6Z4qXe + inserted_at: "2025-01-01T00:00:00Z" + status: ok + step_ref: email_step_1 + step_type: channel + properties: + __typename: + description: The typename of the schema. + example: WorkflowRecipientRunEvent + type: string + x-struct: null + x-validate: null + attempt: + description: The attempt number of the workflow recipient run event. Increments for each retry. + example: 1 + type: integer + x-struct: null + x-validate: null + data: + additionalProperties: true + description: Event-specific data associated with the event. + nullable: true + type: object + x-struct: null + x-validate: null + event: + description: The type of event that occurred. + example: message_enqueued + type: string + x-struct: null + x-validate: null + id: + description: The unique identifier for the event. + example: 2FVHPWxRqNuXQ9krvNP5A6Z4qXe + type: string + x-struct: null + x-validate: null + inserted_at: + description: Timestamp when the resource was created. + example: "2025-01-01T00:00:00Z" + format: date-time + type: string + x-struct: null + x-validate: null + status: + description: Whether the event represents a successful or error state. + enum: + - ok + - error + example: ok + type: string + x-struct: null + x-validate: null + step_ref: + description: The reference of the workflow step associated with this event. + example: email_step_1 + nullable: true + type: string + x-struct: null + x-validate: null + step_type: + description: The type of workflow step associated with this event. + example: channel + nullable: true + type: string + x-struct: null + x-validate: null + required: + - __typename + - id + - event + - status + - inserted_at + title: WorkflowRecipientRunEvent + type: object + x-struct: Elixir.SwitchboardWeb.V1.Specs.WorkflowRecipientRunEvent + x-validate: null CreateSchedulesRequest: description: A request to create a schedule. example: @@ -3487,6 +3657,7 @@ components: active: true bypass_global_group_limit: false channel_id: 51b92f90-1504-4fda-95c1-495a3883bc4d + dashboard_url: https://dashboard.knock.app/~/guides/nps-survey id: 53595157-2fac-4a17-8dd7-e6603e32cb3a inserted_at: "2025-09-30T14:54:44.217756Z" key: nps-survey @@ -3522,6 +3693,7 @@ components: active: true bypass_global_group_limit: false channel_id: 51b92f90-1504-4fda-95c1-495a3883bc4d + dashboard_url: https://dashboard.knock.app/~/guides/changelog-card id: 4fc4503e-ef8b-473a-ae07-14800639d30c inserted_at: "2025-10-07T19:41:06.215233Z" key: changelog-card @@ -3647,6 +3819,11 @@ components: type: string x-struct: null x-validate: null + dashboard_url: + description: URL to this guide in the Knock dashboard + type: string + x-struct: null + x-validate: null id: description: The unique identifier for the guide. format: uuid @@ -4617,6 +4794,114 @@ components: type: array x-struct: null x-validate: null + WorkflowRecipientRun: + description: A workflow recipient run represents an individual execution of a workflow for a specific recipient. + example: + __typename: WorkflowRecipientRun + actor: user_456 + error_count: 0 + id: 550e8400-e29b-41d4-a716-446655440000 + inserted_at: "2025-01-01T00:00:00Z" + recipient: user_123 + status: completed + tenant: tenant_abc + trigger_source: + cancellation_key: comment-123-user-456 + type: api + updated_at: "2025-01-01T00:05:00Z" + workflow: comment-created + workflow_run_id: 660e8400-e29b-41d4-a716-446655440000 + properties: + __typename: + description: The typename of the schema. + example: WorkflowRecipientRun + type: string + x-struct: null + x-validate: null + actor: + anyOf: + - $ref: '#/components/schemas/RecipientReference' + - nullable: true + x-struct: null + x-validate: null + description: The actor who triggered the workflow recipient run. + x-struct: null + x-validate: null + error_count: + description: The number of errors encountered during the workflow recipient run. + example: 0 + type: integer + x-struct: null + x-validate: null + id: + description: The unique identifier for the workflow recipient run (per-recipient). + format: uuid + type: string + x-struct: null + x-validate: null + inserted_at: + description: Timestamp when the resource was created. + example: "2025-01-01T00:00:00Z" + format: date-time + type: string + x-struct: null + x-validate: null + recipient: + $ref: '#/components/schemas/RecipientReference' + status: + description: The current status of the workflow recipient run. One of `queued`, `processing`, `paused`, `completed`, or `cancelled`. + enum: + - queued + - processing + - paused + - completed + - cancelled + example: completed + type: string + x-struct: null + x-validate: null + tenant: + description: The tenant associated with the workflow recipient run. + example: tenant_abc + nullable: true + type: string + x-struct: null + x-validate: null + trigger_source: + $ref: '#/components/schemas/WorkflowRecipientRunTriggerSource' + updated_at: + description: The timestamp when the resource was last updated. + example: "2025-01-01T00:05:00Z" + format: date-time + type: string + x-struct: null + x-validate: null + workflow: + description: The key of the workflow that was executed. + example: comment-created + type: string + x-struct: null + x-validate: null + workflow_run_id: + description: The identifier for the top-level workflow run shared across all recipients in a single trigger. + format: uuid + type: string + x-struct: null + x-validate: null + required: + - __typename + - id + - workflow_run_id + - workflow + - status + - recipient + - trigger_source + - inserted_at + - updated_at + title: WorkflowRecipientRun + type: object + x-struct: Elixir.SwitchboardWeb.V1.Specs.WorkflowRecipientRun + x-validate: null Activity: description: An activity associated with a workflow trigger request. Messages produced after a [batch step](/designing-workflows/batch-function) can be associated with one or more activities. Non-batched messages will always be associated with a single activity. example: @@ -5260,6 +5545,53 @@ components: type: object x-struct: Elixir.SwitchboardWeb.V1.Specs.MsTeamsChannelData.IncomingWebhookConnection x-validate: null + WorkflowRecipientRunDetail: + allOf: + - $ref: '#/components/schemas/WorkflowRecipientRun' + - properties: + events: + description: A list of events that occurred during the workflow recipient run. + items: + $ref: '#/components/schemas/WorkflowRecipientRunEvent' + type: array + x-struct: null + x-validate: null + required: + - events + type: object + x-struct: null + x-validate: null + description: A single workflow recipient run with its events. + example: + __typename: WorkflowRecipientRun + actor: user_456 + error_count: 0 + events: + - __typename: WorkflowRecipientRunEvent + attempt: 1 + data: + channel_type: email + message_id: 2FVHPWxRqNuXQ9krvNP5A6Z4qXe + event: message_enqueued + id: 2FVHPWxRqNuXQ9krvNP5A6Z4qXe + inserted_at: "2025-01-01T00:00:00Z" + status: ok + step_ref: email_step_1 + step_type: channel + id: 550e8400-e29b-41d4-a716-446655440000 + inserted_at: "2025-01-01T00:00:00Z" + recipient: user_123 + status: completed + tenant: tenant_abc + trigger_source: + cancellation_key: comment-123-user-456 + type: api + updated_at: "2025-01-01T00:05:00Z" + workflow: comment-created + workflow_run_id: 660e8400-e29b-41d4-a716-446655440000 + title: WorkflowRecipientRunDetail + x-struct: Elixir.SwitchboardWeb.V1.Specs.WorkflowRecipientRunDetail + x-validate: null PushChannelDataDevicesOnly: description: Push channel data. example: @@ -6628,6 +6960,7 @@ components: - is_timestamp_on_or_after_date - is_timestamp_before_date - is_timestamp_between + - is_between - is_audience_member - is_not_audience_member example: equal_to @@ -11285,6 +11618,39 @@ paths: - Guides - Users x-ratelimit-tier: 2 + /v1/users/{user_id}/guides/engagements/reset: + put: + callbacks: {} + description: Resets the engagement state of a guide for a user, removing the guide's engagement log entry so the next interaction creates a fresh engagement. + operationId: resetUserGuideEngagement + parameters: + - description: The unique identifier of the user. + in: path + name: user_id + required: true + schema: + type: string + x-struct: null + x-validate: null + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GuideActionRequest' + description: Params + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/GuideActionResponse' + description: OK + summary: Reset guide engagement + tags: + - Guides + - Users + x-ratelimit-tier: 2 /v1/objects/{collection}/{object_id}/preferences: get: callbacks: {} @@ -12841,32 +13207,109 @@ paths: response = knock.messages.get_content("message_id") puts(response) - /v1/users/{user_id}/preferences/{id}/workflows: - put: + /v1/workflow_recipient_runs/{id}: + get: callbacks: {} - deprecated: true - description: Updates the workflows in a specific user preference set. This operation is deprecated. - operationId: updateUserPreferenceWorkflows - parameters: [] + description: Returns a single workflow recipient run with its associated events. + operationId: getWorkflowRecipientRun + parameters: + - description: The unique identifier for the workflow recipient run (per-recipient). + in: path + name: id + required: true + schema: + type: string + x-struct: null + x-validate: null responses: "200": content: application/json: schema: - $ref: '#/components/schemas/PreferenceSet' + $ref: '#/components/schemas/WorkflowRecipientRunDetail' description: OK - summary: Update workflows in preference set + summary: Get a workflow recipient run tags: - - Users - - Preferences - x-ratelimit-tier: 3 - /v1/objects/{collection}/{object_id}/preferences/{id}/categories: - put: - callbacks: {} - deprecated: true - description: Updates the category preferences for an object's preference set. - operationId: updateObjectPreferenceCategories - parameters: [] + - Workflow recipient runs + x-ratelimit-tier: 2 + x-retention-policy: true + x-stainless-snippets: + typescript: |- + import Knock from '@knocklabs/node'; + + const client = new Knock({ + apiKey: process.env['KNOCK_API_KEY'], // This is the default and can be omitted + }); + + const workflowRecipientRunDetail = await client.workflowRecipientRuns.get('id'); + + console.log(workflowRecipientRunDetail); + python: |- + import os + from knockapi import Knock + + client = Knock( + api_key=os.environ.get("KNOCK_API_KEY"), # This is the default and can be omitted + ) + workflow_recipient_run_detail = client.workflow_recipient_runs.get( + "id", + ) + print(workflow_recipient_run_detail) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-go" + "github.com/knocklabs/knock-go/option" + ) + + func main() { + client := knock.NewClient( + option.WithAPIKey("My API Key"), + ) + workflowRecipientRunDetail, err := client.WorkflowRecipientRuns.Get(context.TODO(), "id") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", workflowRecipientRunDetail) + } + ruby: |- + require "knockapi" + + knock = Knockapi::Client.new(api_key: "My API Key") + + workflow_recipient_run_detail = knock.workflow_recipient_runs.get("id") + + puts(workflow_recipient_run_detail) + /v1/users/{user_id}/preferences/{id}/workflows: + put: + callbacks: {} + deprecated: true + description: Updates the workflows in a specific user preference set. This operation is deprecated. + operationId: updateUserPreferenceWorkflows + parameters: [] + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PreferenceSet' + description: OK + summary: Update workflows in preference set + tags: + - Users + - Preferences + x-ratelimit-tier: 3 + /v1/objects/{collection}/{object_id}/preferences/{id}/categories: + put: + callbacks: {} + deprecated: true + description: Updates the category preferences for an object's preference set. + operationId: updateObjectPreferenceCategories + parameters: [] responses: "200": content: @@ -13321,6 +13764,167 @@ paths: bulk_operation = knock.tenants.bulk.delete(tenant_ids: ["string"]) puts(bulk_operation) + /v1/workflow_recipient_runs: + get: + callbacks: {} + description: Returns a paginated list of workflow recipient runs for the current environment. + operationId: listWorkflowRecipientRuns + parameters: + - description: The cursor to fetch entries after. + in: query + name: after + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: The cursor to fetch entries before. + in: query + name: before + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: The number of items per page (defaults to 50). + in: query + name: page_size + required: false + schema: + type: integer + x-struct: null + x-validate: null + - description: Limits the results to workflow recipient runs for the given workflow key. + in: query + name: workflow + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: Limits the results to workflow recipient runs with the given status. + in: query + name: status[] + required: false + schema: + items: + enum: + - queued + - processing + - paused + - completed + - cancelled + type: string + x-struct: null + x-validate: null + type: array + x-struct: null + x-validate: null + - description: Limits the results to workflow recipient runs for the given tenant. + in: query + name: tenant + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: Limits the results to workflow recipient runs that have errors. + in: query + name: has_errors + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: Limits the results to workflow recipient runs for the given recipient. Accepts a user ID string or an object reference with `id` and `collection`. + in: query + name: recipient + required: false + schema: + $ref: '#/components/schemas/RecipientReference' + - description: Limits the results to workflow recipient runs started after the given date. + example: "2025-01-01T00:00:00Z" + in: query + name: starting_at + required: false + schema: + format: date-time + type: string + x-struct: null + x-validate: null + - description: Limits the results to workflow recipient runs started before the given date. + example: "2025-01-01T00:00:00Z" + in: query + name: ending_at + required: false + schema: + format: date-time + type: string + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ListWorkflowRecipientRunsResponse' + description: OK + summary: List workflow recipient runs + tags: + - Workflow recipient runs + x-ratelimit-tier: 2 + x-retention-policy: true + x-stainless-snippets: + typescript: |- + import Knock from '@knocklabs/node'; + + const client = new Knock({ + apiKey: process.env['KNOCK_API_KEY'], // This is the default and can be omitted + }); + + // Automatically fetches more pages as needed. + for await (const workflowRecipientRun of client.workflowRecipientRuns.list()) { + console.log(workflowRecipientRun.id); + } + python: |- + import os + from knockapi import Knock + + client = Knock( + api_key=os.environ.get("KNOCK_API_KEY"), # This is the default and can be omitted + ) + page = client.workflow_recipient_runs.list() + page = page.items[0] + print(page.id) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-go" + "github.com/knocklabs/knock-go/option" + ) + + func main() { + client := knock.NewClient( + option.WithAPIKey("My API Key"), + ) + page, err := client.WorkflowRecipientRuns.List(context.TODO(), knock.WorkflowRecipientRunListParams{}) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) + } + ruby: |- + require "knockapi" + + knock = Knockapi::Client.new(api_key: "My API Key") + + page = knock.workflow_recipient_runs.list + + puts(page) /v1/messages/{message_id}/activities: get: callbacks: {} @@ -16326,84 +16930,6 @@ paths: - Messages x-ratelimit-tier: 2 x-retention-policy: true - /v1/users/{user_id}/in-app-messages/{channel_id}/{message_type}: - get: - callbacks: {} - description: Returns a paginated list of in-app messages for a specific user and channel type. - operationId: getUserInAppMessages - parameters: - - description: The user ID. - in: path - name: user_id - required: true - schema: - type: string - x-struct: null - x-validate: null - - description: The channel ID. - in: path - name: channel_id - required: true - schema: - format: uuid - type: string - x-struct: null - x-validate: null - - description: The type of in-app messages to retrieve. - in: query - name: message_type - required: true - schema: - type: string - x-struct: null - x-validate: null - - description: The tenant ID to filter messages by. - in: query - name: tenant - required: false - schema: - type: string - x-struct: null - x-validate: null - - description: The cursor to fetch entries after. - in: query - name: after - required: false - schema: - type: string - x-struct: null - x-validate: null - - description: The cursor to fetch entries before. - in: query - name: before - required: false - schema: - type: string - x-struct: null - x-validate: null - - description: The number of items per page (defaults to 50). - in: query - name: page_size - required: false - schema: - type: integer - x-struct: null - x-validate: null - responses: - "200": - content: - application/json: - schema: - additionalProperties: true - type: object - x-struct: null - x-validate: null - description: OK - summary: List in-app messages - tags: - - In-app messages - - Users - x-ratelimit-tier: 2 /v1/users: get: callbacks: {} @@ -17401,6 +17927,98 @@ paths: puts(bulk_operation) /v1/objects/{collection}/{object_id}/preferences/{id}: + delete: + callbacks: {} + description: Unsets the preference set for the object, removing it entirely. + operationId: deleteObjectPreferenceSet + parameters: + - description: Unique identifier for the object. + in: path + name: object_id + required: true + schema: + type: string + x-struct: null + x-validate: null + - description: The collection this object belongs to. + in: path + name: collection + required: true + schema: + type: string + x-struct: null + x-validate: null + - description: Unique identifier for the preference set. + in: path + name: id + required: true + schema: + default: default + example: default + type: string + x-struct: null + x-validate: null + responses: + "204": + description: No Content + summary: Delete object preference set + tags: + - Objects + - Preferences + x-ratelimit-tier: 3 + x-stainless-snippets: + typescript: |- + import Knock from '@knocklabs/node'; + + const client = new Knock({ + apiKey: process.env['KNOCK_API_KEY'], // This is the default and can be omitted + }); + + await client.objects.unsetPreferences('collection', 'object_id', 'default'); + python: |- + import os + from knockapi import Knock + + client = Knock( + api_key=os.environ.get("KNOCK_API_KEY"), # This is the default and can be omitted + ) + client.objects.unset_preferences( + collection="collection", + object_id="object_id", + id="default", + ) + go: | + package main + + import ( + "context" + + "github.com/knocklabs/knock-go" + "github.com/knocklabs/knock-go/option" + ) + + func main() { + client := knock.NewClient( + option.WithAPIKey("My API Key"), + ) + err := client.Objects.UnsetPreferences( + context.TODO(), + "collection", + "object_id", + "default", + ) + if err != nil { + panic(err.Error()) + } + } + ruby: |- + require "knockapi" + + knock = Knockapi::Client.new(api_key: "My API Key") + + result = knock.objects.unset_preferences("collection", "object_id", "default") + + puts(result) get: callbacks: {} description: Returns the preference set for the specified object and preference set `id`. @@ -17917,6 +18535,88 @@ paths: puts(message) /v1/users/{user_id}/preferences/{id}: + delete: + callbacks: {} + description: Unsets the preference set for the user, removing it entirely. + operationId: deleteUserPreferenceSet + parameters: + - description: The unique identifier of the user. + in: path + name: user_id + required: true + schema: + type: string + x-struct: null + x-validate: null + - description: Unique identifier for the preference set. + in: path + name: id + required: true + schema: + default: default + example: default + type: string + x-struct: null + x-validate: null + responses: + "204": + description: No Content + summary: Delete user preference set + tags: + - Users + - Preferences + x-ratelimit-tier: 3 + x-stainless-snippets: + typescript: |- + import Knock from '@knocklabs/node'; + + const client = new Knock({ + apiKey: process.env['KNOCK_API_KEY'], // This is the default and can be omitted + }); + + await client.users.unsetPreferences('user_id', 'default'); + python: |- + import os + from knockapi import Knock + + client = Knock( + api_key=os.environ.get("KNOCK_API_KEY"), # This is the default and can be omitted + ) + client.users.unset_preferences( + user_id="user_id", + id="default", + ) + go: | + package main + + import ( + "context" + + "github.com/knocklabs/knock-go" + "github.com/knocklabs/knock-go/option" + ) + + func main() { + client := knock.NewClient( + option.WithAPIKey("My API Key"), + ) + err := client.Users.UnsetPreferences( + context.TODO(), + "user_id", + "default", + ) + if err != nil { + panic(err.Error()) + } + } + ruby: |- + require "knockapi" + + knock = Knockapi::Client.new(api_key: "My API Key") + + result = knock.users.unset_preferences("user_id", "default") + + puts(result) get: callbacks: {} description: Retrieves a specific preference set for a user identified by the preference set ID. @@ -18887,6 +19587,8 @@ tags: name: Tenants - description: A user is an individual from your system, represented in Knock. They are most commonly a recipient of a notification. name: Users + - description: A workflow run represents an individual execution of a workflow for a specific recipient. + name: Workflow recipient runs - description: Operations for triggering and canceling workflow executions. name: Workflow Triggers - description: A workflow is a structured set of steps that is triggered to produce notifications sent over channels. diff --git a/data/specs/api/stainless.yml b/data/specs/api/stainless.yml index aa30ec9de..1fee8e541 100644 --- a/data/specs/api/stainless.yml +++ b/data/specs/api/stainless.yml @@ -71,6 +71,10 @@ resources: type: http endpoint: put /v1/users/{user_id}/preferences/{id} positional_params: [ user_id, id ] + unset_preferences: + type: http + endpoint: delete /v1/users/{user_id}/preferences/{id} + positional_params: [ user_id, id ] get_channel_data: type: http endpoint: get /v1/users/{user_id}/channel_data/{channel_id} @@ -147,6 +151,10 @@ resources: type: http endpoint: put /v1/objects/{collection}/{object_id}/preferences/{id} positional_params: [ collection, object_id, id ] + unset_preferences: + type: http + endpoint: delete /v1/objects/{collection}/{object_id}/preferences/{id} + positional_params: [ collection, object_id, id ] list_schedules: type: http endpoint: get /v1/objects/{collection}/{id}/schedules @@ -272,6 +280,18 @@ resources: methods: trigger: post /v1/workflows/{key}/trigger cancel: post /v1/workflows/{key}/cancel + workflow_recipient_runs: + models: + workflow_recipient_run: '#/components/schemas/WorkflowRecipientRun' + workflow_recipient_run_detail: '#/components/schemas/WorkflowRecipientRunDetail' + workflow_recipient_run_event: '#/components/schemas/WorkflowRecipientRunEvent' + methods: + list: + endpoint: get /v1/workflow_recipient_runs + get: + type: http + endpoint: get /v1/workflow_recipient_runs/{id} + positional_params: [ id ] schedules: models: schedule: '#/components/schemas/Schedule' @@ -306,7 +326,8 @@ targets: package_name: "@knocklabs/node" production_repo: knocklabs/knock-node publish: - npm: true + npm: + auth_method: oidc skip: false go: package_name: knock @@ -474,3 +495,15 @@ readme: type: request endpoint: get /v1/users params: {} +unspecified_endpoints: + - post /v1/notify/cancel + - put /v1/users/{user_id}/preferences/{id}/workflows/{key} + - put /v1/users/{user_id}/preferences/{id}/channel_types + - post /v1/notify + - put /v1/objects/{collection}/{object_id}/preferences/{id}/channel_types + - put /v1/users/{user_id}/preferences/{id}/channel_types/{type} + - put /v1/users/{user_id}/preferences/{id}/categories + - put /v1/objects/{collection}/{object_id}/preferences/{id}/categories + - put /v1/users/{user_id}/preferences/{id}/workflows + - put /v1/objects/{collection}/{object_id}/preferences/{id}/workflows/{key} + - put /v1/objects/{collection}/{object_id}/preferences/{id}/categories/{key} \ No newline at end of file diff --git a/data/specs/mapi/customizations.yml b/data/specs/mapi/customizations.yml index 93d73cf0c..9780d3cd6 100644 --- a/data/specs/mapi/customizations.yml +++ b/data/specs/mapi/customizations.yml @@ -84,3 +84,11 @@ resources: name: Authentication description: |- Authentication methods for the Knock management API. + members: + name: Members + description: |- + Members are the users who have access to the Knock account. + audiences: + name: Audiences + description: |- + [Audiences](/concepts/audiences) are user segments that you can use to target users for workflows, guides, and broadcasts. diff --git a/data/specs/mapi/openapi.yml b/data/specs/mapi/openapi.yml index 5801e9102..089f7d2b0 100644 --- a/data/specs/mapi/openapi.yml +++ b/data/specs/mapi/openapi.yml @@ -1,6 +1,117 @@ components: responses: {} schemas: + PreviewTemplateResponse: + description: A response to a template preview request. + example: + content_type: email + result: success + template: + html_body:

      Welcome to Acme!

      + subject: Hello John + text_body: Welcome to Acme! + properties: + content_type: + description: The content type of the preview. + enum: + - email + - in_app_feed + - push + - chat + - sms + type: string + x-struct: null + x-validate: null + errors: + description: A list of errors encountered during rendering. Present when result is "error". + items: + description: A rendering error with optional location information. + example: + field: html_content + line: 1 + message: 'Reason: expected end of string, line: 1' + properties: + field: + description: The template field that caused the error, if available. + nullable: true + type: string + x-struct: null + x-validate: null + line: + description: The line number where the error occurred, if available. + nullable: true + type: integer + x-struct: null + x-validate: null + message: + description: A human-readable description of the error. + type: string + x-struct: null + x-validate: null + required: + - message + type: object + x-struct: null + x-validate: null + nullable: true + type: array + x-struct: null + x-validate: null + result: + description: The result of the preview. + enum: + - success + - error + type: string + x-struct: null + x-validate: null + template: + anyOf: + - $ref: '#/components/schemas/EmailTemplate' + - $ref: '#/components/schemas/InAppFeedTemplate' + - $ref: '#/components/schemas/PushTemplate' + - $ref: '#/components/schemas/ChatTemplate' + - $ref: '#/components/schemas/SmsTemplate' + description: The rendered template, ready to be previewed. + type: object + x-struct: null + x-validate: null + required: + - result + - content_type + title: PreviewTemplateResponse + type: object + x-struct: Elixir.ControlWeb.V1.Specs.PreviewTemplateResponse + x-validate: null + RequestTemplateQueryParamsArray: + description: A list of key-value pairs for the request query params. Each object should contain key and value fields with string values. + example: + - key: key + value: value + items: + properties: + key: + description: The key of the query param. + example: key + type: string + x-struct: null + x-validate: null + value: + description: The value of the query param. + example: value + type: string + x-struct: null + x-validate: null + required: + - key + - value + type: object + x-struct: null + x-validate: null + title: RequestTemplateQueryParamsArray + type: array + x-struct: null + x-validate: null SmsChannelSettings: description: SMS channel settings. Only used as configuration as part of a workflow channel step. example: @@ -20,6 +131,12 @@ components: description: Wraps the EmailLayoutRequest request under the email_layout key. example: email_layout: + branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' footer_links: - text: Example url: http://example.com @@ -43,11 +160,17 @@ components: - $ref: '#/components/schemas/WorkflowSmsStep' - $ref: '#/components/schemas/WorkflowPushStep' - $ref: '#/components/schemas/WorkflowEmailStep' + - $ref: '#/components/schemas/WorkflowAIAgentStep' - $ref: '#/components/schemas/WorkflowDelayStep' - $ref: '#/components/schemas/WorkflowBatchStep' - $ref: '#/components/schemas/WorkflowFetchStep' + - $ref: '#/components/schemas/WorkflowUpdateDataStep' + - $ref: '#/components/schemas/WorkflowUpdateObjectStep' + - $ref: '#/components/schemas/WorkflowUpdateTenantStep' + - $ref: '#/components/schemas/WorkflowUpdateUserStep' - $ref: '#/components/schemas/WorkflowThrottleStep' - $ref: '#/components/schemas/WorkflowBranchStep' + - $ref: '#/components/schemas/WorkflowRandomCohortStep' - $ref: '#/components/schemas/WorkflowTriggerWorkflowStep' description: A step within a workflow. Each workflow step, regardless of its type, share a common set of core attributes (`type`, `ref`, `name`, `description`, `conditions`). example: @@ -71,6 +194,13 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.WorkflowStep x-validate: null + RequestTemplateQueryParamsString: + description: A template string that should resolve to a JSON object representing the query params. + example: '{{ data.request_query_params }}' + title: RequestTemplateQueryParamsString + type: string + x-struct: null + x-validate: null EmailImageBlock: description: An image block in an email template. example: @@ -190,6 +320,9 @@ components: example: entries: - description: This is a description of my variable. + environment_values: + development: dev_value + production: prod_value inserted_at: "2021-01-01T00:00:00Z" key: my_variable type: public @@ -217,6 +350,80 @@ components: type: object x-struct: null x-validate: null + ChannelGroupRuleRequest: + description: A rule that determines if a channel should be executed as part of a channel group. + example: + channel_key: email-channel + index: 0 + rule_type: always + properties: + argument: + description: For conditional rules, the value to compare against. + nullable: true + type: string + x-struct: null + x-validate: null + channel_key: + description: The key of the channel this rule applies to. + type: string + x-struct: null + x-validate: null + index: + description: The order index of this rule within the channel group. + type: integer + x-struct: null + x-validate: null + operator: + description: For conditional rules, the operator to apply. + enum: + - equal_to + - not_equal_to + - greater_than + - less_than + - greater_than_or_equal_to + - less_than_or_equal_to + - contains + - not_contains + - contains_all + - not_contains_all + - is_timestamp_before + - is_timestamp_on_or_after + - is_timestamp_between + - is_between + - empty + - not_empty + - exists + - not_exists + - is_timestamp + - is_audience_member + - is_not_audience_member + example: equal_to + nullable: true + type: string + x-struct: null + x-validate: null + rule_type: + description: The type of rule (if = conditional, unless = negative conditional, always = always apply). + enum: + - if + - unless + - always + type: string + x-struct: null + x-validate: null + variable: + description: For conditional rules, the variable to evaluate. + nullable: true + type: string + x-struct: null + x-validate: null + required: + - channel_key + - rule_type + title: ChannelGroupRuleRequest + type: object + x-struct: Elixir.ControlWeb.V1.Specs.ChannelGroupRuleRequest + x-validate: null PreviewWorkflowTemplateResponse: description: A response to a preview workflow template request. example: @@ -269,6 +476,102 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.PreviewWorkflowTemplateResponse x-validate: null + RequestTemplateHeadersString: + description: A template string that should resolve to a JSON object representing the headers. + example: '{{ data.request_headers }}' + title: RequestTemplateHeadersString + type: string + x-struct: null + x-validate: null + WorkflowUpdateTenantStep: + description: An update tenant step. Updates properties of a specific tenant referenced in the workflow. + example: + description: Update tenant step description. + name: Update tenant + ref: update_tenant_1 + settings: + recipient_gid: gid://Object/$tenants/tenant-123 + recipient_mode: reference + update_properties: '{"name": "Updated Tenant"}' + type: update_tenant + properties: + conditions: + anyOf: + - $ref: '#/components/schemas/ConditionGroup' + - nullable: true + x-struct: null + x-validate: null + description: A conditions object that describes one or more conditions to be met in order for the step to be executed. + type: object + x-struct: null + x-validate: null + description: + description: An arbitrary string attached to a workflow step. Useful for adding notes about the workflow for internal purposes. + example: Update tenant step description. + nullable: true + type: string + x-struct: null + x-validate: null + name: + description: A name for the workflow step. + example: Update tenant + nullable: true + type: string + x-struct: null + x-validate: null + ref: + description: The reference key of the workflow step. Must be unique per workflow. + example: update_tenant_1 + type: string + x-struct: null + x-validate: null + settings: + description: The settings for the update tenant step. + properties: + recipient_gid: + description: 'The global identifier (GID) of the tenant to update. Required when recipient_mode is ''reference''. Format: gid://Object/$tenants/{id}' + example: gid://Object/$tenants/tenant-123 + nullable: true + type: string + x-struct: null + x-validate: null + recipient_mode: + description: The recipient mode determining how the tenant is selected. 'current' uses the workflow's current tenant. 'reference' uses a specific tenant ID. + enum: + - current + - reference + example: reference + type: string + x-struct: null + x-validate: null + update_properties: + description: A JSON string or Liquid template that evaluates to the properties to update on the tenant. + example: '{"name": "Updated Tenant", "status": "active"}' + type: string + x-struct: null + x-validate: null + required: + - recipient_mode + - update_properties + type: object + x-struct: null + x-validate: null + type: + description: The type of the workflow step. + enum: + - update_tenant + example: update_tenant + type: string + x-struct: null + x-validate: null + required: + - type + - ref + - settings + title: WorkflowUpdateTenantStep + type: object + x-struct: Elixir.ControlWeb.V1.Specs.WorkflowUpdateTenantStep + x-validate: null WorkflowDelayStep: description: A delay function step. Read more in the [docs](https://docs.knock.app/designing-workflows/delay-function). example: @@ -372,13 +675,20 @@ components: x-validate: null headers: description: The headers of the request. Can be a template string or a list of key-value pairs. + example: + - key: X-API-Key + value: "1234567890" oneOf: - description: A template string that should resolve to a JSON object representing the headers. example: '{{ data.request_headers }}' + title: RequestTemplateHeadersString type: string x-struct: null x-validate: null - description: A list of key-value pairs for the request headers. Each object should contain key and value fields with string values. + example: + - key: X-API-Key + value: "1234567890" items: properties: key: @@ -399,9 +709,11 @@ components: type: object x-struct: null x-validate: null + title: RequestTemplateHeadersArray type: array x-struct: null x-validate: null + title: RequestTemplateHeaders type: object x-struct: null x-validate: null @@ -419,13 +731,20 @@ components: x-validate: null query_params: description: The query params of the request. Can be a template string or a list of key-value pairs. + example: + - key: key + value: value oneOf: - description: A template string that should resolve to a JSON object representing the query params. example: '{{ data.request_query_params }}' + title: RequestTemplateQueryParamsString type: string x-struct: null x-validate: null - description: A list of key-value pairs for the request query params. Each object should contain key and value fields with string values. + example: + - key: key + value: value items: properties: key: @@ -446,9 +765,11 @@ components: type: object x-struct: null x-validate: null + title: RequestTemplateQueryParamsArray type: array x-struct: null x-validate: null + title: RequestTemplateQueryParams type: object x-struct: null x-validate: null @@ -727,8 +1048,19 @@ components: example: partial: content:

      Hello, world!

      + description: This is a test partial + input_schema: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text name: My Partial type: html + visual_block_enabled: true properties: partial: $ref: '#/components/schemas/PartialRequest' @@ -841,7 +1173,8 @@ components: x-struct: null x-validate: null description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true type: string x-struct: null x-validate: null @@ -869,6 +1202,12 @@ components: type: array x-struct: null x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true + type: string + x-struct: null + x-validate: null required: description: Whether the field is required. example: true @@ -929,7 +1268,14 @@ components: x-struct: null x-validate: null description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true + type: string + x-struct: null + x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true type: string x-struct: null x-validate: null @@ -997,6 +1343,11 @@ components: type: string x-struct: null x-validate: null + tenant: + description: An optional tenant identifier to scope the translation to a specific tenant. + type: string + x-struct: null + x-validate: null updated_at: description: The timestamp of when the translation was last updated. format: date-time @@ -1220,7 +1571,14 @@ components: description: Settings for the button field. properties: description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true + type: string + x-struct: null + x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true type: string x-struct: null x-validate: null @@ -1528,6 +1886,7 @@ components: label: Image Action settings: description: A link to open when the paywall image is clicked + placeholder: A placeholder for the image action required: true type: text alt: @@ -1535,6 +1894,7 @@ components: label: Alt Text settings: description: A description of the paywall image + placeholder: A placeholder for the alt text required: true type: text key: image_field @@ -1548,6 +1908,7 @@ components: label: Image URL settings: description: The URL of the paywall image + placeholder: A placeholder for the image URL required: true type: url properties: @@ -1572,7 +1933,14 @@ components: description: Settings for the image field. properties: description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true + type: string + x-struct: null + x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true type: string x-struct: null x-validate: null @@ -1628,6 +1996,7 @@ components: - $ref: '#/components/schemas/MessageTypeBooleanField' - $ref: '#/components/schemas/MessageTypeButtonField' - $ref: '#/components/schemas/MessageTypeImageField' + - $ref: '#/components/schemas/MessageTypeJsonField' - $ref: '#/components/schemas/MessageTypeMarkdownField' - $ref: '#/components/schemas/MessageTypeMultiSelectField' - $ref: '#/components/schemas/MessageTypeSelectField' @@ -1661,6 +2030,12 @@ components: EmailLayoutRequest: description: A request to update or create an email layout. example: + branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' footer_links: - text: Example url: http://example.com @@ -1668,6 +2043,8 @@ components: name: Transactional text_layout: Hello, world! properties: + branding_overrides: + $ref: '#/components/schemas/BrandingOverrides' footer_links: description: A list of one or more items to show in the footer of the email layout. items: @@ -1694,11 +2071,17 @@ components: x-struct: null x-validate: null html_layout: - description: The complete HTML content of the email layout. + description: The complete HTML or MJML content of the email layout. nullable: false type: string x-struct: null x-validate: null + is_mjml: + description: Whether this layout uses MJML format. When true, html_layout must contain tags. + nullable: true + type: boolean + x-struct: null + x-validate: null name: description: The friendly name of this email layout. nullable: false @@ -1719,37 +2102,101 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.EmailLayoutRequest x-validate: null - PartialRequest: - description: A partial object with attributes to update or create a partial. + WrappedChannelGroupResponse: + description: Wraps the ChannelGroup response under the `channel_group` key. example: - content:

      Hello, world!

      - name: My Partial - type: html - properties: + channel_group: + channel_rules: + - channel: + archived_at: null + created_at: "2021-01-01T00:00:00Z" + custom_icon_url: null + id: 01234567-89ab-cdef-0123-456789abcdef + key: my-sendgrid-channel + name: My Sendgrid Channel + provider: sendgrid + type: email + updated_at: "2021-01-01T00:00:00Z" + created_at: "2021-01-01T00:00:00Z" + index: 0 + rule_type: always + updated_at: "2021-01-01T00:00:00Z" + channel_type: push + created_at: "2021-01-01T00:00:00Z" + key: push-group + name: Push Notification Group + operator: any + source: user + updated_at: "2021-01-01T00:00:00Z" + properties: + channel_group: + $ref: '#/components/schemas/ChannelGroup' + required: + - channel_group + title: WrappedChannelGroupResponse + type: object + x-struct: null + x-validate: null + PartialRequest: + description: A partial object with attributes to update or create a partial. + example: + content:

      Hello, world!

      + description: This is a test partial + input_schema: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text + name: My Partial + type: html + visual_block_enabled: true + properties: content: - description: The content of the partial. + description: The partial content. type: string x-struct: null x-validate: null description: - description: The description of the partial. - nullable: true + description: An arbitrary string attached to a partial object. Useful for adding notes about the partial for internal purposes. Maximum of 280 characters allowed. type: string x-struct: null x-validate: null icon_name: - description: The name of the icon to be used in the visual editor. Only relevant when `visual_block_enabled` is `true`. - nullable: true + description: The name of the icon to be used in the visual editor. type: string x-struct: null x-validate: null + input_schema: + description: The field types available for the partial. + items: + anyOf: + - $ref: '#/components/schemas/MessageTypeBooleanField' + - $ref: '#/components/schemas/MessageTypeButtonField' + - $ref: '#/components/schemas/MessageTypeImageField' + - $ref: '#/components/schemas/MessageTypeJsonField' + - $ref: '#/components/schemas/MessageTypeMarkdownField' + - $ref: '#/components/schemas/MessageTypeMultiSelectField' + - $ref: '#/components/schemas/MessageTypeSelectField' + - $ref: '#/components/schemas/MessageTypeTextField' + - $ref: '#/components/schemas/MessageTypeTextareaField' + - $ref: '#/components/schemas/MessageTypeUrlField' + type: object + x-struct: null + x-validate: null + type: array + x-struct: null + x-validate: null name: - description: The name of the partial. + description: A name for the partial. Must be at maximum 255 characters in length. type: string x-struct: null x-validate: null type: - description: The type of the partial. + description: The partial type. One of 'html', 'json', 'markdown', 'text'. enum: - html - text @@ -1760,8 +2207,6 @@ components: x-validate: null visual_block_enabled: description: Indicates whether the partial can be used in the visual editor. Only applies to HTML partials. - example: false - nullable: true type: boolean x-struct: null x-validate: null @@ -1787,6 +2232,96 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.ConditionGroup x-validate: null + MessageTypeJsonField: + description: A JSON field used in a message type. + example: + key: json_field + label: JSON Field + settings: + default: + key: value + description: A description of the JSON field + placeholder: A placeholder for the JSON field + required: true + schema: + properties: + key: + type: string + required: + - key + type: json + properties: + key: + description: The unique key of the field. + example: key + type: string + x-struct: null + x-validate: null + label: + description: The label of the field. + example: Label + nullable: true + type: string + x-struct: null + x-validate: null + settings: + description: Settings for the json field. + properties: + default: + description: The default value of the JSON field. + example: + key: value + nullable: true + type: object + x-struct: null + x-validate: null + description: + example: A description of the field, used in the UI as a hint text. + nullable: true + type: string + x-struct: null + x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true + type: string + x-struct: null + x-validate: null + required: + description: Whether the field is required. + example: true + type: boolean + x-struct: null + x-validate: null + schema: + description: A JSON schema used to validate the structure of the JSON provided. Must be a valid JSON schema. + example: + properties: + key: + type: string + nullable: true + type: object + x-struct: null + x-validate: null + type: object + x-struct: null + x-validate: null + type: + description: The type of the field. + enum: + - json + example: json + type: string + x-struct: null + x-validate: null + required: + - type + - key + - label + title: MessageTypeJsonField + type: object + x-struct: Elixir.ControlWeb.V1.Specs.MessageTypes.JsonField + x-validate: null BroadcastRequest: description: A broadcast request for upserting a broadcast. example: @@ -1863,6 +2398,7 @@ components: - $ref: '#/components/schemas/WorkflowEmailStep' - $ref: '#/components/schemas/WorkflowBranchStep' - $ref: '#/components/schemas/WorkflowDelayStep' + - $ref: '#/components/schemas/WorkflowRandomCohortStep' description: A step within a broadcast. Each step, regardless of its type, share a common set of core attributes (`type`, `ref`, `name`, `description`, `conditions`). type: object x-struct: null @@ -1922,6 +2458,64 @@ components: type: object x-struct: null x-validate: null + StaticAudienceRequest: + description: Request body for creating/updating a static audience. + example: + description: Manually managed list of beta testers + name: Beta users + type: static + properties: + description: + description: A description of the audience. + nullable: true + type: string + x-struct: null + x-validate: null + name: + description: The name of the audience. + type: string + x-struct: null + x-validate: null + type: + description: The type of audience. Set to `static` for static audiences. + enum: + - static + type: string + x-struct: null + x-validate: null + required: + - type + - name + title: StaticAudienceRequest + type: object + x-struct: Elixir.ControlWeb.V1.Specs.StaticAudienceRequest + x-validate: null + WrappedAudienceResponse: + description: Wraps the Audience response under the `audience` key. + example: + audience: + created_at: "2024-01-15T10:30:00Z" + description: Customers who signed up in the last 30 days. + environment: development + key: new-customers + name: New customers + segments: + - conditions: + - argument: 30_days_ago + operator: greater_than + property: recipient.created_at + sha: a1b2c3d4e5f6 + type: dynamic + updated_at: "2024-06-20T14:45:00Z" + properties: + audience: + $ref: '#/components/schemas/Audience' + required: + - audience + title: WrappedAudienceResponse + type: object + x-struct: null + x-validate: null ArchiveGuideResponse: description: The response from archiving a guide. example: @@ -2520,6 +3114,7 @@ components: description: A description of the text field max_length: 100 min_length: 10 + placeholder: A placeholder for the text field required: true type: text properties: @@ -2547,7 +3142,8 @@ components: x-struct: null x-validate: null description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true type: string x-struct: null x-validate: null @@ -2561,6 +3157,12 @@ components: type: integer x-struct: null x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true + type: string + x-struct: null + x-validate: null required: description: Whether the field is required. example: true @@ -2613,12 +3215,18 @@ components: text_body: 'Hello, {{ recipient.name }}! Welcome to {{ vars.app_name }} Get started here: {{ data.sign_in_url }}.' properties: html_body: - description: An HTML template for the email body. **Required** if `visual_blocks` is not provided. Only one of `html_body` or `visual_blocks` should be set. Supports Liquid templating with variables like `{{ recipient.name }}`, `{{ actor.name }}`, `{{ vars.app_name }}`, `{{ data.custom_field }}`, and `{{ tenant.name }}`. See the [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) for available variables. + description: An HTML or MJML template for the email body. **Required** if `visual_blocks` is not provided. Only one of `html_body` or `visual_blocks` should be set. When `is_mjml` is true, this must contain MJML components. Supports Liquid templating with variables like `{{ recipient.name }}`, `{{ actor.name }}`, `{{ vars.app_name }}`, `{{ data.custom_field }}`, and `{{ tenant.name }}`. See the [template variables reference](https://docs.knock.app/designing-workflows/template-editor/variables) for available variables. example:

      Hello, world!

      nullable: true type: string x-struct: null x-validate: null + is_mjml: + description: Whether this template uses MJML format. When true, the template content will be compiled from MJML to HTML. Only valid when the selected layout is also MJML or when no layout is selected. + nullable: true + type: boolean + x-struct: null + x-validate: null settings: description: The [settings](https://docs.knock.app/integrations/email/settings) for the email template. Must be supplied with at least `layout_key`. example: @@ -2863,6 +3471,128 @@ components: type: object x-struct: null x-validate: null + AudienceCondition: + description: A condition to evaluate for audience membership. + example: + argument: premium + operator: equal_to + property: recipient.plan + properties: + argument: + description: The argument to compare against. Can be a static value (string, number, boolean) or a dynamic path expression. + example: premium + nullable: true + type: string + x-struct: null + x-validate: null + operator: + description: The operator to use when evaluating the condition. + enum: + - equal_to + - not_equal_to + - greater_than + - less_than + - greater_than_or_equal_to + - less_than_or_equal_to + - contains + - not_contains + - contains_all + - not_contains_all + - is_timestamp_before + - is_timestamp_on_or_after + - is_timestamp_between + - is_between + - empty + - not_empty + - exists + - not_exists + - is_timestamp + - is_audience_member + - is_not_audience_member + example: equal_to + type: string + x-struct: null + x-validate: null + property: + description: The property to be evaluated. Properties are dynamic values using path expressions like `recipient.plan` or `recipient.created_at`. + example: recipient.plan + type: string + x-struct: null + x-validate: null + required: + - property + - operator + title: AudienceCondition + type: object + x-struct: Elixir.ControlWeb.V1.Specs.AudienceCondition + x-validate: null + WorkflowRandomCohortStep: + description: An experiment step. Deterministically assigns recipients to percentage-based cohorts for A/B testing and experimentation. + example: + cohort_branches: + - name: Control + percentage: "50" + steps: [] + terminates: false + - name: Variant + percentage: "50" + steps: [] + terminates: false + description: Experiment step description. + name: Experiment + ref: experiment_1 + type: random_cohort + properties: + cohort_branches: + description: A list of cohort branches. Must have between 2 and 10 branches, and percentages must sum to 100. + items: + $ref: '#/components/schemas/WorkflowRandomCohortStepBranch' + type: array + x-struct: null + x-validate: null + cohort_key: + description: The key used to deterministically assign recipients to cohorts. Defaults to the recipient ID if not provided. + example: tenant.id + nullable: true + type: string + x-struct: null + x-validate: null + description: + description: An arbitrary string attached to a workflow step. Useful for adding notes about the workflow for internal purposes. + example: Experiment step description. + nullable: true + type: string + x-struct: null + x-validate: null + name: + description: A name for the workflow step. + example: Experiment + nullable: true + type: string + x-struct: null + x-validate: null + ref: + description: The reference key of the workflow step. Must be unique per workflow. + example: experiment_1 + type: string + x-struct: null + x-validate: null + type: + description: The type of step. + enum: + - random_cohort + example: random_cohort + type: string + x-struct: null + x-validate: null + required: + - type + - ref + - cohort_branches + title: WorkflowRandomCohortStep + type: object + x-struct: Elixir.ControlWeb.V1.Specs.WorkflowRandomCohortStep + x-validate: null WorkflowThrottleStep: description: A throttle function step. Read more in the [docs](https://docs.knock.app/designing-workflows/throttle-function). example: @@ -3346,16 +4076,69 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.WhoamiResponse x-validate: null - MessageTypeUrlField: - description: A URL field used in a message type. + ChannelGroupRequest: + description: A request to create or update a channel group. example: - key: url_field - label: URL Field - settings: - description: A description of the URL field - required: true - type: url - properties: + channel_rules: + - channel_key: push-fcm + index: 0 + rule_type: always + channel_type: push + name: Push Notification Group + operator: any + properties: + channel_rules: + description: Rules for determining which channels should be used. + items: + $ref: '#/components/schemas/ChannelGroupRuleRequest' + type: array + x-struct: null + x-validate: null + channel_type: + description: The type of channels contained in this group. + enum: + - email + - in_app + - in_app_feed + - in_app_guide + - sms + - push + - chat + - http + type: string + x-struct: null + x-validate: null + name: + description: The human-readable name of the channel group. + type: string + x-struct: null + x-validate: null + operator: + description: Determines how the channel rules are applied ('any' means any rule can match, 'all' means all rules must match). + enum: + - any + - all + type: string + x-struct: null + x-validate: null + required: + - name + - channel_type + title: ChannelGroupRequest + type: object + x-struct: Elixir.ControlWeb.V1.Specs.ChannelGroupRequest + x-validate: null + MessageTypeUrlField: + description: A URL field used in a message type. + example: + key: url_field + label: URL Field + settings: + description: A description of the URL field + placeholder: A placeholder for the URL field + required: true + type: url + properties: key: description: The unique key of the field. example: key @@ -3380,7 +4163,14 @@ components: x-struct: null x-validate: null description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true + type: string + x-struct: null + x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true type: string x-struct: null x-validate: null @@ -3424,6 +4214,27 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.PushChannelSettings x-validate: null + WrappedAudienceRequestRequest: + description: Wraps the AudienceRequest request under the audience key. + example: + audience: + description: Users on the premium plan + name: Premium users + segments: + - conditions: + - argument: premium + operator: equal_to + property: recipient.plan + type: dynamic + properties: + audience: + $ref: '#/components/schemas/AudienceRequest' + required: + - audience + title: WrappedAudienceRequestRequest + type: object + x-struct: null + x-validate: null WrappedCommitResponse: description: Wraps the Commit response under the `commit` key. example: @@ -3451,6 +4262,12 @@ components: description: Wraps the EmailLayout response under the `email_layout` key. example: email_layout: + branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' created_at: "2021-01-01T00:00:00Z" environment: development footer_links: @@ -3471,15 +4288,45 @@ components: type: object x-struct: null x-validate: null + RequestTemplateHeadersArray: + description: A list of key-value pairs for the request headers. Each object should contain key and value fields with string values. + example: + - key: X-API-Key + value: "1234567890" + items: + properties: + key: + description: The key of the header. + example: X-API-Key + type: string + x-struct: null + x-validate: null + value: + description: The value of the header. + example: "1234567890" + type: string + x-struct: null + x-validate: null + required: + - key + - value + type: object + x-struct: null + x-validate: null + title: RequestTemplateHeadersArray + type: array + x-struct: null + x-validate: null GuideActivationUrlPattern: - description: A rule that controls when a guide should be shown based on the user's location in the application. + description: A rule that controls when a guide should be shown based on the user's location in the application. At least one of `pathname` or `search` must be provided. example: directive: allow pathname: /dashboard/* + search: tab=settings properties: directive: default: allow - description: Whether to allow or block the guide at the specified pathname. + description: Whether to allow or block the guide at the specified location. enum: - allow - block @@ -3492,9 +4339,14 @@ components: type: string x-struct: null x-validate: null + search: + description: The URL query string pattern to match against (without the leading '?'). Supports URLPattern API syntax. + example: tab=settings&* + type: string + x-struct: null + x-validate: null required: - directive - - pathname title: GuideActivationUrlPattern type: object x-struct: Elixir.ControlWeb.V1.Specs.GuideActivationUrlPattern @@ -3598,6 +4450,14 @@ components: type: string x-struct: null x-validate: null + environment_settings: + additionalProperties: + $ref: '#/components/schemas/ChannelEnvironmentSettings' + description: Per-environment settings for this channel, keyed by environment slug (e.g., 'development', 'production'). Only included when requested via the `include` parameter or when retrieving a single channel. + nullable: true + type: object + x-struct: null + x-validate: null id: description: The unique identifier for the channel. type: string @@ -3746,6 +4606,63 @@ components: type: object x-struct: null x-validate: null + Member: + description: A member of the account. + example: + created_at: "2024-01-15T10:30:00Z" + id: d4b8e8e0-1234-5678-9abc-def012345678 + role: admin + updated_at: "2024-06-20T14:45:00Z" + user: + avatar_url: https://www.gravatar.com/avatar/abc123 + created_at: "2024-01-10T08:00:00Z" + email: jane@example.com + id: a1b2c3d4-5678-9abc-def0-123456789abc + name: Jane Doe + updated_at: "2024-06-18T12:00:00Z" + properties: + created_at: + description: The timestamp of when the member joined the account. + format: date-time + type: string + x-struct: null + x-validate: null + id: + description: The unique identifier of the member. + format: uuid + type: string + x-struct: null + x-validate: null + role: + description: The member's role in the account. + enum: + - owner + - admin + - member + - production_only_member + - billing + - support + type: string + x-struct: null + x-validate: null + updated_at: + description: The timestamp of when the member was last updated. + format: date-time + type: string + x-struct: null + x-validate: null + user: + $ref: '#/components/schemas/MemberUser' + required: + - id + - role + - user + - created_at + - updated_at + title: Member + type: object + x-struct: Elixir.ControlWeb.V1.Specs.Member + x-validate: null ObjectRecipientReference: description: An object reference. example: @@ -3769,6 +4686,75 @@ components: type: object x-struct: null x-validate: null + StaticAudience: + description: A static audience where members are explicitly added or removed via the API. + example: + created_at: "2024-01-15T10:30:00Z" + description: Users participating in the beta program. + environment: development + key: beta-testers + name: Beta testers + sha: a1b2c3d4e5f6 + type: static + updated_at: "2024-06-20T14:45:00Z" + properties: + created_at: + description: The timestamp of when the audience was created. + format: date-time + type: string + x-struct: null + x-validate: null + description: + description: A description of the audience. + nullable: true + type: string + x-struct: null + x-validate: null + environment: + description: The slug of the environment in which the audience exists. + type: string + x-struct: null + x-validate: null + key: + description: The unique key of the audience. + type: string + x-struct: null + x-validate: null + name: + description: The name of the audience. + type: string + x-struct: null + x-validate: null + sha: + description: The SHA hash of the audience data. + nullable: true + type: string + x-struct: null + x-validate: null + type: + description: The type of audience. Always `static` for static audiences. + enum: + - static + type: string + x-struct: null + x-validate: null + updated_at: + description: The timestamp of when the audience was last updated. + format: date-time + type: string + x-struct: null + x-validate: null + required: + - key + - type + - name + - environment + - created_at + - updated_at + title: StaticAudience + type: object + x-struct: Elixir.ControlWeb.V1.Specs.StaticAudience + x-validate: null PromoteAllResponse: description: The response from promoting all changes. example: @@ -3786,6 +4772,43 @@ components: type: object x-struct: null x-validate: null + PaginatedMemberResponse: + description: A paginated list of Member. Contains a list of entries and page information. + example: + entries: + - created_at: "2024-01-15T10:30:00Z" + id: d4b8e8e0-1234-5678-9abc-def012345678 + role: admin + updated_at: "2024-06-20T14:45:00Z" + user: + avatar_url: https://www.gravatar.com/avatar/abc123 + created_at: "2024-01-10T08:00:00Z" + email: jane@example.com + id: a1b2c3d4-5678-9abc-def0-123456789abc + name: Jane Doe + updated_at: "2024-06-18T12:00:00Z" + page_info: + after: null + before: null + page_size: 25 + properties: + entries: + description: A list of entries. + items: + $ref: '#/components/schemas/Member' + nullable: false + type: array + x-struct: null + x-validate: null + page_info: + $ref: '#/components/schemas/PageInfo' + required: + - entries + - page_info + title: PaginatedMemberResponse + type: object + x-struct: null + x-validate: null PageInfo: description: The information about a paginated result. example: @@ -3837,6 +4860,9 @@ components: description: An environment variable object. example: description: This is a description of my variable. + environment_values: + development: dev_value + production: prod_value inserted_at: "2021-01-01T00:00:00Z" key: my_variable type: public @@ -3849,6 +4875,16 @@ components: type: string x-struct: null x-validate: null + environment_values: + additionalProperties: + nullable: true + type: string + x-struct: null + x-validate: null + description: A map of environment slugs to their override values. Only present for project-scoped responses. + type: object + x-struct: null + x-validate: null inserted_at: description: The timestamp of when the variable was created. format: date-time @@ -3876,13 +4912,13 @@ components: x-struct: null x-validate: null value: - description: The value of the variable. + description: The default value of the variable. For secret variables, this is obfuscated. + nullable: true type: string x-struct: null x-validate: null required: - key - - value - type - inserted_at - updated_at @@ -3890,6 +4926,95 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.Variable x-validate: null + WorkflowUpdateUserStep: + description: An update user step. Updates properties of a specific user referenced in the workflow. + example: + description: Update user step description. + name: Update user + ref: update_user_1 + settings: + recipient_gid: gid://Object/$users/user-123 + recipient_mode: reference + update_properties: '{"name": "Updated User"}' + type: update_user + properties: + conditions: + anyOf: + - $ref: '#/components/schemas/ConditionGroup' + - nullable: true + x-struct: null + x-validate: null + description: A conditions object that describes one or more conditions to be met in order for the step to be executed. + type: object + x-struct: null + x-validate: null + description: + description: An arbitrary string attached to a workflow step. Useful for adding notes about the workflow for internal purposes. + example: Update user step description. + nullable: true + type: string + x-struct: null + x-validate: null + name: + description: A name for the workflow step. + example: Update user + nullable: true + type: string + x-struct: null + x-validate: null + ref: + description: The reference key of the workflow step. Must be unique per workflow. + example: update_user_1 + type: string + x-struct: null + x-validate: null + settings: + description: The settings for the update user step. + properties: + recipient_gid: + description: 'The global identifier (GID) of the user to update. Required when recipient_mode is ''reference''. Format: gid://Object/$users/{id}' + example: gid://Object/$users/user-123 + nullable: true + type: string + x-struct: null + x-validate: null + recipient_mode: + description: The recipient mode determining how the user is selected. 'current' uses the workflow's current user. 'reference' uses a specific user ID. + enum: + - current + - reference + example: reference + type: string + x-struct: null + x-validate: null + update_properties: + description: A JSON string or Liquid template that evaluates to the properties to update on the user. + example: '{"name": "Updated User", "email": "user@example.com"}' + type: string + x-struct: null + x-validate: null + required: + - recipient_mode + - update_properties + type: object + x-struct: null + x-validate: null + type: + description: The type of the workflow step. + enum: + - update_user + example: update_user + type: string + x-struct: null + x-validate: null + required: + - type + - ref + - settings + title: WorkflowUpdateUserStep + type: object + x-struct: Elixir.ControlWeb.V1.Specs.WorkflowUpdateUserStep + x-validate: null PaginatedTranslationResponse: description: A paginated list of Translation. Contains a list of entries and page information. example: @@ -4167,8 +5292,15 @@ components: source: user updated_at: "2021-01-01T00:00:00Z" properties: - channel_rules: - description: Rules for determining which channels should be used. + archived_at: + description: The timestamp of when the channel group was archived (soft deleted). + format: date-time + nullable: true + type: string + x-struct: null + x-validate: null + channel_rules: + description: Rules for determining which channels should be used. items: $ref: '#/components/schemas/ChannelGroupRule' type: array @@ -4290,12 +5422,13 @@ components: - contains_all - not_contains_all - is_timestamp_before - - is_timestamp_after - - is_timestamp_before_date - - is_timestamp_after_date + - is_timestamp_on_or_after - is_timestamp_between + - is_between - empty - not_empty + - exists + - not_exists - is_timestamp - is_audience_member - is_not_audience_member @@ -4466,6 +5599,7 @@ components: - $ref: '#/components/schemas/WorkflowEmailStep' - $ref: '#/components/schemas/WorkflowBranchStep' - $ref: '#/components/schemas/WorkflowDelayStep' + - $ref: '#/components/schemas/WorkflowRandomCohortStep' description: A step within a broadcast. Each step, regardless of its type, share a common set of core attributes (`type`, `ref`, `name`, `description`, `conditions`). type: object x-struct: null @@ -4604,6 +5738,76 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.WorkflowWebhookStep x-validate: null + WorkflowUpdateDataStep: + description: An update data function step. Merges data into the workflow's `data` scope for use in subsequent steps. + example: + description: Update data step description. + name: Update data + ref: update_data_1 + settings: + data: '{"key": "value"}' + type: update_data + properties: + conditions: + anyOf: + - $ref: '#/components/schemas/ConditionGroup' + - nullable: true + x-struct: null + x-validate: null + description: A conditions object that describes one or more conditions to be met in order for the step to be executed. + type: object + x-struct: null + x-validate: null + description: + description: An arbitrary string attached to a workflow step. Useful for adding notes about the workflow for internal purposes. + example: Update data step description. + nullable: true + type: string + x-struct: null + x-validate: null + name: + description: A name for the workflow step. + example: Update data + nullable: true + type: string + x-struct: null + x-validate: null + ref: + description: The reference key of the workflow step. Must be unique per workflow. + example: update_data_1 + type: string + x-struct: null + x-validate: null + settings: + description: The settings for the update data step. + properties: + data: + description: A JSON string or Liquid template that evaluates to the data to merge into the workflow's data scope. + example: '{"key": "value"}' + type: string + x-struct: null + x-validate: null + required: + - data + type: object + x-struct: null + x-validate: null + type: + description: The type of the workflow step. + enum: + - update_data + example: update_data + type: string + x-struct: null + x-validate: null + required: + - type + - ref + - settings + title: WorkflowUpdateDataStep + type: object + x-struct: Elixir.ControlWeb.V1.Specs.WorkflowUpdateDataStep + x-validate: null WrappedWorkflowResponse: description: Wraps the Workflow response under the `workflow` key. example: @@ -4694,6 +5898,12 @@ components: EmailLayout: description: A versioned email layout used within an environment. example: + branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' created_at: "2021-01-01T00:00:00Z" environment: development footer_links: @@ -4706,6 +5916,8 @@ components: text_layout: Hello, world! updated_at: "2021-01-01T00:00:00Z" properties: + branding_overrides: + $ref: '#/components/schemas/BrandingOverrides' created_at: description: The timestamp of when the email layout was created. format: date-time @@ -4741,10 +5953,15 @@ components: x-struct: null x-validate: null html_layout: - description: The complete HTML content of the email layout. + description: The complete HTML or MJML content of the email layout. type: string x-struct: null x-validate: null + is_mjml: + description: Whether this layout uses MJML format. When true, html_layout must contain tags. + type: boolean + x-struct: null + x-validate: null key: description: The unique key for this email layout. type: string @@ -4930,6 +6147,26 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.ChatTemplate x-validate: null + WrappedChannelGroupRequestRequest: + description: Wraps the ChannelGroupRequest request under the channel_group key. + example: + channel_group: + channel_rules: + - channel_key: push-fcm + index: 0 + rule_type: always + channel_type: push + name: Push Notification Group + operator: any + properties: + channel_group: + $ref: '#/components/schemas/ChannelGroupRequest' + required: + - channel_group + title: WrappedChannelGroupRequestRequest + type: object + x-struct: null + x-validate: null Guide: description: A guide defines an in-app guide that can be displayed to users based on priority and other conditions. example: @@ -5084,6 +6321,101 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.Guide x-validate: null + ArchiveAudienceResponse: + description: The response from archiving an audience. + example: + result: success + properties: + result: + description: The result of the archive operation. + example: success + type: string + x-struct: null + x-validate: null + required: + - result + title: ArchiveAudienceResponse + type: object + x-struct: null + x-validate: null + WorkflowUpdateObjectStep: + description: An update object step. Updates properties of a specific object referenced in the workflow. + example: + description: Update object step description. + name: Update object + ref: update_object_1 + settings: + recipient_gid: gid://Object/projects/123 + update_properties: '{"status": "active"}' + type: update_object + properties: + conditions: + anyOf: + - $ref: '#/components/schemas/ConditionGroup' + - nullable: true + x-struct: null + x-validate: null + description: A conditions object that describes one or more conditions to be met in order for the step to be executed. + type: object + x-struct: null + x-validate: null + description: + description: An arbitrary string attached to a workflow step. Useful for adding notes about the workflow for internal purposes. + example: Update object step description. + nullable: true + type: string + x-struct: null + x-validate: null + name: + description: A name for the workflow step. + example: Update object + nullable: true + type: string + x-struct: null + x-validate: null + ref: + description: The reference key of the workflow step. Must be unique per workflow. + example: update_object_1 + type: string + x-struct: null + x-validate: null + settings: + description: The settings for the update object step. + properties: + recipient_gid: + description: 'The global identifier (GID) of the object to update. Format: gid://Object/{collection}/{id}' + example: gid://Object/projects/123 + type: string + x-struct: null + x-validate: null + update_properties: + description: A JSON string or Liquid template that evaluates to the properties to update on the object. + example: '{"status": "active", "updated_at": "{{ timestamp }}"}' + type: string + x-struct: null + x-validate: null + required: + - recipient_gid + - update_properties + type: object + x-struct: null + x-validate: null + type: + description: The type of the workflow step. + enum: + - update_object + example: update_object + type: string + x-struct: null + x-validate: null + required: + - type + - ref + - settings + title: WorkflowUpdateObjectStep + type: object + x-struct: Elixir.ControlWeb.V1.Specs.WorkflowUpdateObjectStep + x-validate: null WrappedMessageTypeRequestRequest: description: Wraps the MessageTypeRequest request under the message_type key. example: @@ -5112,6 +6444,45 @@ components: type: object x-struct: null x-validate: null + PaginatedAudienceResponse: + description: A paginated list of Audience. Contains a list of entries and page information. + example: + entries: + - created_at: "2024-01-15T10:30:00Z" + description: Customers who signed up in the last 30 days. + environment: development + key: new-customers + name: New customers + segments: + - conditions: + - argument: 30_days_ago + operator: greater_than + property: recipient.created_at + sha: a1b2c3d4e5f6 + type: dynamic + updated_at: "2024-06-20T14:45:00Z" + page_info: + after: null + before: null + page_size: 25 + properties: + entries: + description: A list of entries. + items: + $ref: '#/components/schemas/Audience' + nullable: false + type: array + x-struct: null + x-validate: null + page_info: + $ref: '#/components/schemas/PageInfo' + required: + - entries + - page_info + title: PaginatedAudienceResponse + type: object + x-struct: null + x-validate: null ShowWorkflowResponse: description: A workflow object. example: @@ -5192,7 +6563,7 @@ components: x-validate: null created_by: anyOf: - - $ref: '#/components/schemas/WorkflowUserInfo' + - $ref: '#/components/schemas/MemberUser' - nullable: true x-struct: null x-validate: null @@ -5279,7 +6650,7 @@ components: x-validate: null updated_by: anyOf: - - $ref: '#/components/schemas/WorkflowUserInfo' + - $ref: '#/components/schemas/MemberUser' - nullable: true x-struct: null x-validate: null @@ -5518,6 +6889,62 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.WorkflowBranchStep x-validate: null + DynamicAudienceRequest: + description: Request body for creating/updating a dynamic audience. + example: + description: Users on the premium plan + name: Premium users + segments: + - conditions: + - argument: premium + operator: equal_to + property: recipient.plan + type: dynamic + properties: + description: + description: A description of the audience. + nullable: true + type: string + x-struct: null + x-validate: null + name: + description: The name of the audience. + type: string + x-struct: null + x-validate: null + segments: + description: A list of segments that define the dynamic audience membership criteria. Each segment contains one or more conditions joined by AND. Multiple segments are joined by OR. + items: + properties: + conditions: + description: A list of conditions within this segment, joined by AND. + items: + $ref: '#/components/schemas/AudienceCondition' + type: array + x-struct: null + x-validate: null + required: + - conditions + type: object + x-struct: null + x-validate: null + type: array + x-struct: null + x-validate: null + type: + description: The type of audience. Set to `dynamic` for dynamic audiences. + enum: + - dynamic + type: string + x-struct: null + x-validate: null + required: + - type + - name + title: DynamicAudienceRequest + type: object + x-struct: Elixir.ControlWeb.V1.Specs.DynamicAudienceRequest + x-validate: null WebhookTemplate: description: A webhook template. By default, a webhook step will use the request settings you configured in your webhook channel. You can override this as you see fit on a per-step basis. example: @@ -5704,37 +7131,6 @@ components: type: object x-struct: null x-validate: null - WorkflowUserInfo: - description: Information about a user within the Knock dashboard. Not to be confused with an external user (recipient) of a workflow. - example: - email: john@example.com - id: user_123 - name: John Doe - properties: - email: - description: The user's email address. - format: email - type: string - x-struct: null - x-validate: null - id: - description: The user's unique identifier. - type: string - x-struct: null - x-validate: null - name: - description: The user's name. - nullable: true - type: string - x-struct: null - x-validate: null - required: - - id - - email - title: WorkflowUserInfo - type: object - x-struct: Elixir.ControlWeb.V1.Specs.WorkflowUserInfo - x-validate: null EmailMarkdownBlock: description: A markdown block in an email template. example: @@ -5864,6 +7260,7 @@ components: This is **bold** and this is *italic*. description: A description of the markdown field + placeholder: A placeholder for the markdown field required: true type: markdown properties: @@ -5893,7 +7290,14 @@ components: x-struct: null x-validate: null description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true + type: string + x-struct: null + x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true type: string x-struct: null x-validate: null @@ -5931,6 +7335,7 @@ components: description: A description of the textarea field max_length: 1000 min_length: 10 + placeholder: A placeholder for the textarea field required: true type: textarea properties: @@ -5958,7 +7363,8 @@ components: x-struct: null x-validate: null description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true type: string x-struct: null x-validate: null @@ -5972,6 +7378,12 @@ components: type: integer x-struct: null x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true + type: string + x-struct: null + x-validate: null required: description: Whether the field is required. example: true @@ -5997,33 +7409,213 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.MessageTypes.TextareaField x-validate: null - GuideRequest: - description: A request to create or update a guide. + BrandingOverrides: + additionalProperties: false + description: Overrides to apply against account branding variables in an email layout, including dark mode-specific values. example: - activation_url_patterns: - - directive: allow - pathname: /dashboard/* - channel_key: in-app-guide - description: A guide to help users get started with the application - name: Getting Started Guide - steps: - - name: Welcome to the App - ref: welcome-step - schema_key: tooltip - schema_semver: 1.0.0 - schema_variant_key: default - values: - text_field: value - target_audience_id: null - target_property_conditions: - all: - - argument: some_property - operator: equal_to - variable: recipient.property + dark_icon_url: https://cdn.example.com/icon-dark.png + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + icon_url: https://cdn.example.com/icon-light.png + logo_url: https://cdn.example.com/logo-light.png + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' + primary_text_color: '#111827' + secondary_text_color: '#6B7280' + nullable: true properties: - activation_url_patterns: - description: A list of activation url patterns that describe when the guide should be shown. - items: + dark_icon_url: + description: A URL for a dark mode icon override. + nullable: true + type: string + x-struct: null + x-validate: null + dark_logo_url: + description: A URL for a dark mode logo override. + nullable: true + type: string + x-struct: null + x-validate: null + dark_primary_color: + description: The dark mode primary brand color in hex format. + nullable: true + pattern: ^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$ + type: string + x-struct: null + x-validate: null + dark_primary_color_contrast: + description: The dark mode contrast color for the primary brand color in hex format. + nullable: true + pattern: ^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$ + type: string + x-struct: null + x-validate: null + icon_url: + description: A URL for a light mode icon override. + nullable: true + type: string + x-struct: null + x-validate: null + logo_url: + description: A URL for a light mode logo override. + nullable: true + type: string + x-struct: null + x-validate: null + primary_color: + description: The light mode primary brand color in hex format. + nullable: true + pattern: ^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$ + type: string + x-struct: null + x-validate: null + primary_color_contrast: + description: The light mode contrast color for the primary brand color in hex format. + nullable: true + pattern: ^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$ + type: string + x-struct: null + x-validate: null + primary_text_color: + description: The light mode primary text color in hex format. + nullable: true + pattern: ^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$ + type: string + x-struct: null + x-validate: null + secondary_text_color: + description: The light mode secondary text color in hex format. + nullable: true + pattern: ^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$ + type: string + x-struct: null + x-validate: null + title: BrandingOverrides + type: object + x-struct: Elixir.ControlWeb.V1.Specs.BrandingOverrides + x-validate: null + DynamicAudience: + description: A dynamic audience where membership is determined by segment conditions evaluated at runtime. + example: + created_at: "2024-01-15T10:30:00Z" + description: Customers who signed up in the last 30 days. + environment: development + key: new-customers + name: New customers + segments: + - conditions: + - argument: 30_days_ago + operator: greater_than + property: recipient.created_at + sha: a1b2c3d4e5f6 + type: dynamic + updated_at: "2024-06-20T14:45:00Z" + properties: + created_at: + description: The timestamp of when the audience was created. + format: date-time + type: string + x-struct: null + x-validate: null + description: + description: A description of the audience. + nullable: true + type: string + x-struct: null + x-validate: null + environment: + description: The slug of the environment in which the audience exists. + type: string + x-struct: null + x-validate: null + key: + description: The unique key of the audience. + type: string + x-struct: null + x-validate: null + name: + description: The name of the audience. + type: string + x-struct: null + x-validate: null + segments: + description: A list of segments that define the dynamic audience membership criteria. Each segment contains one or more conditions joined by AND. Multiple segments are joined by OR. + items: + properties: + conditions: + description: A list of conditions within this segment, joined by AND. + items: + $ref: '#/components/schemas/AudienceCondition' + type: array + x-struct: null + x-validate: null + required: + - conditions + type: object + x-struct: null + x-validate: null + type: array + x-struct: null + x-validate: null + sha: + description: The SHA hash of the audience data. + nullable: true + type: string + x-struct: null + x-validate: null + type: + description: The type of audience. Always `dynamic` for dynamic audiences. + enum: + - dynamic + type: string + x-struct: null + x-validate: null + updated_at: + description: The timestamp of when the audience was last updated. + format: date-time + type: string + x-struct: null + x-validate: null + required: + - key + - type + - name + - environment + - created_at + - updated_at + - segments + title: DynamicAudience + type: object + x-struct: Elixir.ControlWeb.V1.Specs.DynamicAudience + x-validate: null + GuideRequest: + description: A request to create or update a guide. + example: + activation_url_patterns: + - directive: allow + pathname: /dashboard/* + channel_key: in-app-guide + description: A guide to help users get started with the application + name: Getting Started Guide + steps: + - name: Welcome to the App + ref: welcome-step + schema_key: tooltip + schema_semver: 1.0.0 + schema_variant_key: default + values: + text_field: value + target_audience_id: null + target_property_conditions: + all: + - argument: some_property + operator: equal_to + variable: recipient.property + properties: + activation_url_patterns: + description: A list of activation url patterns that describe when the guide should be shown. + items: $ref: '#/components/schemas/GuideActivationUrlPattern' type: array x-struct: null @@ -6341,6 +7933,121 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.EmailChannelSettings x-validate: null + PreviewTemplateRequest: + description: A request to preview a template, without requiring a template to be persisted within Knock. + example: + channel_type: email + data: + order_id: 123 + recipient: user_123 + template: + html_body:

      Welcome!

      + settings: + layout_key: default + subject: Hello {{ recipient.name }} + properties: + actor: + anyOf: + - $ref: '#/components/schemas/RecipientReference' + - nullable: true + x-struct: null + x-validate: null + description: The actor to reference in the preview. + x-struct: null + x-validate: null + channel_type: + description: The channel type of the template to preview. + enum: + - email + - sms + - push + - chat + - in_app_feed + type: string + x-struct: null + x-validate: null + data: + additionalProperties: true + description: The data to pass to the template for rendering. + type: object + x-struct: null + x-validate: null + layout: + description: Email layout configuration. Only applicable for email channel type. Falls back to environment default if not provided. + nullable: true + properties: + html_content: + description: Inline HTML content for the layout. Must include `{{ content }}` placeholder. + nullable: true + type: string + x-struct: null + x-validate: null + key: + description: The key of an existing email layout to use. + nullable: true + type: string + x-struct: null + x-validate: null + text_content: + description: Inline text content for the layout. + nullable: true + type: string + x-struct: null + x-validate: null + type: object + x-struct: null + x-validate: null + recipient: + $ref: '#/components/schemas/RecipientReference' + template: + anyOf: + - $ref: '#/components/schemas/EmailTemplate' + - $ref: '#/components/schemas/SmsTemplate' + - $ref: '#/components/schemas/PushTemplate' + - $ref: '#/components/schemas/ChatTemplate' + - $ref: '#/components/schemas/InAppFeedTemplate' + description: The template content to preview. Structure depends on channel_type. + type: object + x-struct: null + x-validate: null + tenant: + description: The tenant to associate with the preview. Must not contain whitespace. + nullable: true + type: string + x-struct: null + x-validate: null + workflow: + description: Optional workflow context for variable hydration. When provided, recipient/actor/tenant are resolved via Knock. + nullable: true + properties: + categories: + description: Workflow categories. + items: + type: string + x-struct: null + x-validate: null + nullable: true + type: array + x-struct: null + x-validate: null + key: + description: The workflow key. + type: string + x-struct: null + x-validate: null + required: + - key + type: object + x-struct: null + x-validate: null + required: + - channel_type + - template + - recipient + title: PreviewTemplateRequest + type: object + x-struct: Elixir.ControlWeb.V1.Specs.PreviewTemplateRequest + x-validate: null SendWindow: description: A send window time for a notification. Describes a single day. example: @@ -6430,40 +8137,95 @@ components: type: object x-struct: null x-validate: null - PreviewWorkflowTemplateRequest: - description: A request to preview a workflow template. + MemberUser: + description: Information about a user within the Knock dashboard. Not to be confused with an external user (recipient) of a workflow. example: - actor: dnedry - data: - park_id: 1 - recipient: dnedry - tenant: acme-corp + avatar_url: https://www.gravatar.com/avatar/abc123 + created_at: "2024-01-10T08:00:00Z" + email: jane@example.com + id: a1b2c3d4-5678-9abc-def0-123456789abc + name: Jane Doe + updated_at: "2024-06-18T12:00:00Z" properties: - actor: - anyOf: - - $ref: '#/components/schemas/RecipientReference' - - nullable: true - x-struct: null - x-validate: null - description: The actor to reference in the the workflow run. - type: object + avatar_url: + description: The URL of the user's avatar image. + nullable: true + type: string x-struct: null x-validate: null - data: - additionalProperties: true - description: The data to pass to the workflow template for rendering. - type: object + created_at: + description: The timestamp of when the user was created. + format: date-time + type: string x-struct: null x-validate: null - recipient: - $ref: '#/components/schemas/RecipientReference' - tenant: - description: The tenant to associate the workflow with. Must not contain whitespace. - nullable: true + email: + description: The user's email address. + format: email type: string x-struct: null x-validate: null - required: + id: + description: The user's unique identifier. + format: uuid + type: string + x-struct: null + x-validate: null + name: + description: The user's display name. + nullable: true + type: string + x-struct: null + x-validate: null + updated_at: + description: The timestamp of when the user was last updated. + format: date-time + type: string + x-struct: null + x-validate: null + required: + - id + - email + - created_at + - updated_at + title: MemberUser + type: object + x-struct: Elixir.ControlWeb.V1.Specs.MemberUser + x-validate: null + PreviewWorkflowTemplateRequest: + description: A request to preview a workflow template. + example: + actor: dnedry + data: + park_id: 1 + recipient: dnedry + tenant: acme-corp + properties: + actor: + anyOf: + - $ref: '#/components/schemas/RecipientReference' + - nullable: true + x-struct: null + x-validate: null + description: The actor to reference in the the workflow run. + type: object + x-struct: null + x-validate: null + data: + additionalProperties: true + description: The data to pass to the workflow template for rendering. + type: object + x-struct: null + x-validate: null + recipient: + $ref: '#/components/schemas/RecipientReference' + tenant: + description: The tenant to associate the workflow with. Must not contain whitespace. + nullable: true + type: string + x-struct: null + x-validate: null + required: - recipient title: PreviewWorkflowTemplateRequest type: object @@ -6577,7 +8339,13 @@ components: description: A paginated list of EmailLayout. Contains a list of entries and page information. example: entries: - - created_at: "2021-01-01T00:00:00Z" + - branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' + created_at: "2021-01-01T00:00:00Z" environment: development footer_links: - text: Example @@ -6610,6 +8378,98 @@ components: type: object x-struct: null x-validate: null + ChannelEnvironmentSettings: + description: Environment-specific settings for a channel. + example: + channel_settings: + from_address: hello@example.com + from_name: Example Inc + id: 550e8400-e29b-41d4-a716-446655440001 + is_sandbox: false + is_valid: true + provider_settings: + api_key: '{{ vars.sendgrid_api_key }}' + check_delivery_status: true + link_tracking: true + open_tracking: true + properties: + channel_settings: + additionalProperties: true + description: Channel-type-specific settings (e.g., from_address for email). Structure varies by channel type. + nullable: true + type: object + x-struct: null + x-validate: null + id: + description: The unique identifier for these environment settings. + format: uuid + type: string + x-struct: null + x-validate: null + is_sandbox: + description: Whether the channel is in sandbox mode for this environment. Sandbox mode may prevent actual message delivery. + type: boolean + x-struct: null + x-validate: null + is_valid: + description: Whether the channel configuration is valid and ready to send messages in this environment. + type: boolean + x-struct: null + x-validate: null + provider_settings: + additionalProperties: true + description: Provider-specific settings (e.g., API keys, credentials). Structure varies by provider. Secret values are obfuscated unless they are Liquid templates. + nullable: true + type: object + x-struct: null + x-validate: null + required: + - id + - is_valid + - is_sandbox + title: ChannelEnvironmentSettings + type: object + x-struct: Elixir.ControlWeb.V1.Specs.ChannelEnvironmentSettings + x-validate: null + WorkflowRandomCohortStepBranch: + description: A cohort branch in an experiment step. + example: + name: Control + percentage: "50" + steps: [] + terminates: false + properties: + name: + description: The name of the cohort branch. + example: Control + type: string + x-struct: null + x-validate: null + percentage: + description: The percentage of recipients to assign to this cohort. Must be between 0 and 100 with at most 1 decimal place. All branch percentages must sum to 100. Sent as a number in requests; returned as a decimal string in responses (e.g. "50", "33.3"). + example: "50" + type: string + x-struct: null + x-validate: null + steps: + description: A list of steps that will be executed for recipients assigned to this cohort. + items: + $ref: '#/components/schemas/WorkflowStep' + type: array + x-struct: null + x-validate: null + terminates: + description: If the workflow should halt at the end of the branch. Defaults to false if not provided. + example: false + type: boolean + x-struct: null + x-validate: null + required: + - percentage + title: WorkflowRandomCohortStepBranch + type: object + x-struct: Elixir.ControlWeb.V1.Specs.WorkflowRandomCohortStep.CohortBranch + x-validate: null RunWorkflowResponse: description: A response to a run workflow request. example: @@ -6627,6 +8487,73 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.RunWorkflowResponse x-validate: null + AudienceRequest: + description: 'An audience object with attributes to create or update an audience. Use `type: static` for audiences with explicitly managed members, or `type: dynamic` for audiences with segment-based membership.' + discriminator: + mapping: + dynamic: '#/components/schemas/DynamicAudienceRequest' + static: '#/components/schemas/StaticAudienceRequest' + propertyName: type + example: + description: Users on the premium plan + name: Premium users + segments: + - conditions: + - argument: premium + operator: equal_to + property: recipient.plan + type: dynamic + oneOf: + - $ref: '#/components/schemas/StaticAudienceRequest' + - $ref: '#/components/schemas/DynamicAudienceRequest' + title: AudienceRequest + type: object + x-struct: Elixir.ControlWeb.V1.Specs.AudienceRequest + x-validate: null + RequestTemplateQueryParams: + description: The query params of the request. Can be a template string or a list of key-value pairs. + example: + - key: key + value: value + oneOf: + - description: A template string that should resolve to a JSON object representing the query params. + example: '{{ data.request_query_params }}' + title: RequestTemplateQueryParamsString + type: string + x-struct: null + x-validate: null + - description: A list of key-value pairs for the request query params. Each object should contain key and value fields with string values. + example: + - key: key + value: value + items: + properties: + key: + description: The key of the query param. + example: key + type: string + x-struct: null + x-validate: null + value: + description: The value of the query param. + example: value + type: string + x-struct: null + x-validate: null + required: + - key + - value + type: object + x-struct: null + x-validate: null + title: RequestTemplateQueryParamsArray + type: array + x-struct: null + x-validate: null + title: RequestTemplateQueryParams + type: object + x-struct: null + x-validate: null Branch: description: A branch object. example: @@ -6675,83 +8602,237 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.Branch x-validate: null - Partial: - description: A partial is a reusable piece of content that can be used in a template. + WorkflowAIAgentStep: + description: An AI agent function step. Fetches data from an AI model and merges it into the workflow's `data` scope for use in later steps. Supports Liquid templating in the prompt. Read more in the [docs](https://docs.knock.app/designing-workflows/ai-agent-function). example: - content:

      Hello, world!

      - description: This is a test partial - environment: development - icon_name: icon-name - input_schema: - - key: text_field - label: My text field - settings: - description: A description of the text field - max_length: 100 - min_length: 10 - required: true - type: text - inserted_at: "2021-01-01T00:00:00Z" - key: my-partial - name: My Partial - type: html - updated_at: "2021-01-01T00:00:00Z" - valid: true - visual_block_enabled: true + name: AI agent step + ref: ai_agent_step + settings: + model: anthropic:claude-haiku-4-5 + request_prompt: You are a helpful assistant. + response_type: text + type: ai_agent properties: - content: - description: The partial content. - type: string + conditions: + anyOf: + - $ref: '#/components/schemas/ConditionGroup' + - nullable: true + x-struct: null + x-validate: null + description: A conditions object that describes one or more conditions to be met in order for the step to be executed. + type: object x-struct: null x-validate: null description: - description: An arbitrary string attached to a partial object. Useful for adding notes about the partial for internal purposes. Maximum of 280 characters allowed. + description: An arbitrary string attached to a workflow step. Useful for adding notes about the workflow for internal purposes. + example: AI agent step description + nullable: true type: string x-struct: null x-validate: null - environment: - description: The slug of the environment in which the partial exists. + name: + description: A name for the workflow step. + example: AI agent step + nullable: true type: string x-struct: null x-validate: null - icon_name: - description: The name of the icon to be used in the visual editor. + ref: + description: The reference key of the workflow step. Must be unique per workflow. + example: ai_1 type: string x-struct: null x-validate: null - input_schema: - description: The field types available for the partial. - items: - anyOf: - - $ref: '#/components/schemas/MessageTypeBooleanField' - - $ref: '#/components/schemas/MessageTypeButtonField' - - $ref: '#/components/schemas/MessageTypeImageField' - - $ref: '#/components/schemas/MessageTypeMarkdownField' - - $ref: '#/components/schemas/MessageTypeMultiSelectField' - - $ref: '#/components/schemas/MessageTypeSelectField' - - $ref: '#/components/schemas/MessageTypeTextField' - - $ref: '#/components/schemas/MessageTypeTextareaField' - - $ref: '#/components/schemas/MessageTypeUrlField' - type: object - x-struct: null - x-validate: null - type: array - x-struct: null - x-validate: null - inserted_at: - description: The timestamp of when the partial was created. - format: date-time - type: string + settings: + description: The settings for the AI agent step. + properties: + halt_on_error: + description: Whether to halt the workflow if the AI fetch fails. + example: true + nullable: true + type: boolean + x-struct: null + x-validate: null + model: + description: The AI model to use in `provider:model` format (e.g. `anthropic:claude-haiku-4-5`, `openai:gpt-5.2-chat-latest`). See the documentation for a list of supported models. + example: anthropic:claude-haiku-4-5 + type: string + x-struct: null + x-validate: null + request_prompt: + description: The prompt template for the AI request. Supports Liquid templating. + example: You are a helpful assistant. + type: string + x-struct: null + x-validate: null + response_schema: + description: A JSON schema string for structured output. Required when `response_type` is `json`. Must not be set when `response_type` is `text`. + example: '{"type": "object", "properties": {"summary": {"type": "string"}}}' + nullable: true + type: string + x-struct: null + x-validate: null + response_type: + description: The type of response to expect from the AI model. + enum: + - text + - json + example: text + type: string + x-struct: null + x-validate: null + web_search_enabled: + description: Whether to enable web search for the AI request. + example: false + nullable: true + type: boolean + x-struct: null + x-validate: null + required: + - model + - response_type + - request_prompt + type: object x-struct: null x-validate: null - key: - description: The unique key string for the partial object. Must be at minimum 3 characters and at maximum 255 characters in length. Must be in the format of ^[a-z0-9_-]+$. + type: + description: The type of the workflow step. + enum: + - ai_agent + example: ai_agent type: string x-struct: null x-validate: null - name: - description: A name for the partial. Must be at maximum 255 characters in length. - type: string + required: + - type + - ref + - settings + title: WorkflowAIAgentStep + type: object + x-struct: Elixir.ControlWeb.V1.Specs.WorkflowAIAgentStep + x-validate: null + RequestTemplateHeaders: + description: The headers of the request. Can be a template string or a list of key-value pairs. + example: + - key: X-API-Key + value: "1234567890" + oneOf: + - description: A template string that should resolve to a JSON object representing the headers. + example: '{{ data.request_headers }}' + title: RequestTemplateHeadersString + type: string + x-struct: null + x-validate: null + - description: A list of key-value pairs for the request headers. Each object should contain key and value fields with string values. + example: + - key: X-API-Key + value: "1234567890" + items: + properties: + key: + description: The key of the header. + example: X-API-Key + type: string + x-struct: null + x-validate: null + value: + description: The value of the header. + example: "1234567890" + type: string + x-struct: null + x-validate: null + required: + - key + - value + type: object + x-struct: null + x-validate: null + title: RequestTemplateHeadersArray + type: array + x-struct: null + x-validate: null + title: RequestTemplateHeaders + type: object + x-struct: null + x-validate: null + Partial: + description: A partial is a reusable piece of content that can be used in a template. + example: + content:

      Hello, world!

      + description: This is a test partial + environment: development + icon_name: icon-name + input_schema: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text + inserted_at: "2021-01-01T00:00:00Z" + key: my-partial + name: My Partial + type: html + updated_at: "2021-01-01T00:00:00Z" + valid: true + visual_block_enabled: true + properties: + content: + description: The partial content. + type: string + x-struct: null + x-validate: null + description: + description: An arbitrary string attached to a partial object. Useful for adding notes about the partial for internal purposes. Maximum of 280 characters allowed. + type: string + x-struct: null + x-validate: null + environment: + description: The slug of the environment in which the partial exists. + type: string + x-struct: null + x-validate: null + icon_name: + description: The name of the icon to be used in the visual editor. + type: string + x-struct: null + x-validate: null + input_schema: + description: The field types available for the partial. + items: + anyOf: + - $ref: '#/components/schemas/MessageTypeBooleanField' + - $ref: '#/components/schemas/MessageTypeButtonField' + - $ref: '#/components/schemas/MessageTypeImageField' + - $ref: '#/components/schemas/MessageTypeJsonField' + - $ref: '#/components/schemas/MessageTypeMarkdownField' + - $ref: '#/components/schemas/MessageTypeMultiSelectField' + - $ref: '#/components/schemas/MessageTypeSelectField' + - $ref: '#/components/schemas/MessageTypeTextField' + - $ref: '#/components/schemas/MessageTypeTextareaField' + - $ref: '#/components/schemas/MessageTypeUrlField' + type: object + x-struct: null + x-validate: null + type: array + x-struct: null + x-validate: null + inserted_at: + description: The timestamp of when the partial was created. + format: date-time + type: string + x-struct: null + x-validate: null + key: + description: The unique key string for the partial object. Must be at minimum 3 characters and at maximum 255 characters in length. Must be in the format of ^[a-z0-9_-]+$. + type: string + x-struct: null + x-validate: null + name: + description: A name for the partial. Must be at maximum 255 characters in length. + type: string x-struct: null x-validate: null type: @@ -7139,7 +9220,8 @@ components: x-struct: null x-validate: null description: - example: A description of the field, used in the UI + example: A description of the field, used in the UI as a hint text. + nullable: true type: string x-struct: null x-validate: null @@ -7167,6 +9249,12 @@ components: type: array x-struct: null x-validate: null + placeholder: + example: A placeholder for the field. + nullable: true + type: string + x-struct: null + x-validate: null required: description: Whether the field is required. example: true @@ -7352,6 +9440,34 @@ components: type: object x-struct: Elixir.ControlWeb.V1.Specs.WorkflowTriggerWorkflowStep x-validate: null + Audience: + description: An audience defines a set of users that can be targeted for notifications. Can be either a `StaticAudience` (members explicitly added/removed) or a `DynamicAudience` (members determined by segment conditions). + discriminator: + mapping: + dynamic: '#/components/schemas/DynamicAudience' + static: '#/components/schemas/StaticAudience' + propertyName: type + example: + created_at: "2024-01-15T10:30:00Z" + description: Customers who signed up in the last 30 days. + environment: development + key: new-customers + name: New customers + segments: + - conditions: + - argument: 30_days_ago + operator: greater_than + property: recipient.created_at + sha: a1b2c3d4e5f6 + type: dynamic + updated_at: "2024-06-20T14:45:00Z" + oneOf: + - $ref: '#/components/schemas/StaticAudience' + - $ref: '#/components/schemas/DynamicAudience' + title: Audience + type: object + x-struct: Elixir.ControlWeb.V1.Specs.Audience + x-validate: null Condition: description: A condition to be evaluated. example: @@ -7380,12 +9496,13 @@ components: - contains_all - not_contains_all - is_timestamp_before - - is_timestamp_after - - is_timestamp_before_date - - is_timestamp_after_date + - is_timestamp_on_or_after - is_timestamp_between + - is_between - empty - not_empty + - exists + - not_exists - is_timestamp - is_audience_member - is_not_audience_member @@ -7574,22 +9691,22 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - environment, err := client.Environments.Get(context.TODO(), "development") - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", environment.HidePiiData) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + environment, err := client.Environments.Get(context.TODO(), "development") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", environment.HidePiiData) } /v1/branches: get: @@ -7697,24 +9814,24 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Branches.List(context.TODO(), knockmapi.BranchListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Branches.List(context.TODO(), knockmapi.BranchListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/workflows/{workflow_key}/validate: put: @@ -7900,40 +10017,40 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Workflows.Validate( - context.TODO(), - "workflow_key", - knockmapi.WorkflowValidateParams{ - Environment: "development", - Workflow: knockmapi.WorkflowValidateParamsWorkflow{ - Name: "My Workflow", - Steps: []knockmapi.WorkflowStepUnionParam{knockmapi.WorkflowStepUnionParam{ - OfWorkflowInAppFeedStep: &knockmapi.WorkflowInAppFeedStepParam{ - Ref: "channel_1", - Template: knockmapi.InAppFeedTemplateParam{ - MarkdownBody: "Hello **{{ recipient.name }}**", - }, - Type: knockmapi.WorkflowInAppFeedStepTypeChannel, - }, - }}, - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Workflow) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Workflows.Validate( + context.TODO(), + "workflow_key", + knockmapi.WorkflowValidateParams{ + Environment: "development", + Workflow: knockmapi.WorkflowValidateParamsWorkflow{ + Name: "My Workflow", + Steps: []knockmapi.WorkflowStepUnionParam{{ + OfWorkflowInAppFeedStep: &knockmapi.WorkflowInAppFeedStepParam{ + Ref: "channel_1", + Template: knockmapi.InAppFeedTemplateParam{ + MarkdownBody: "Hello **{{ recipient.name }}**", + }, + Type: knockmapi.WorkflowInAppFeedStepTypeChannel, + }, + }}, + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Workflow) } /v1/workflows/{workflow_key}/run: put: @@ -8015,31 +10132,31 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Workflows.Run( - context.TODO(), - "workflow_key", - knockmapi.WorkflowRunParams{ - Environment: "development", - Recipients: []knockmapi.WorkflowRunParamsRecipientUnion{knockmapi.WorkflowRunParamsRecipientUnion{ - OfString: knockmapi.String("dnedry"), - }}, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.WorkflowRunID) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Workflows.Run( + context.TODO(), + "workflow_key", + knockmapi.WorkflowRunParams{ + Environment: "development", + Recipients: []knockmapi.WorkflowRunParamsRecipientUnion{{ + OfString: knockmapi.String("dnedry"), + }}, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.WorkflowRunID) } /v1/email_layouts: get: @@ -8113,7 +10230,13 @@ paths: description: A paginated list of EmailLayout. Contains a list of entries and page information. example: entries: - - created_at: "2021-01-01T00:00:00Z" + - branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' + created_at: "2021-01-01T00:00:00Z" environment: development footer_links: - text: Example @@ -8160,7 +10283,7 @@ paths: // Automatically fetches more pages as needed. for await (const emailLayout of client.emailLayouts.list({ environment: 'development' })) { - console.log(emailLayout.created_at); + console.log(emailLayout.branding_overrides); } python: |- import os @@ -8173,29 +10296,29 @@ paths: environment="development", ) page = page.entries[0] - print(page.created_at) + print(page.branding_overrides) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.EmailLayouts.List(context.TODO(), knockmapi.EmailLayoutListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.EmailLayouts.List(context.TODO(), knockmapi.EmailLayoutListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/guides/{guide_key}: delete: @@ -8262,22 +10385,22 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Guides.Archive(context.TODO(), "guide_key") - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Result) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Guides.Archive(context.TODO(), "guide_key") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Result) } get: callbacks: {} @@ -8363,28 +10486,28 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - guide, err := client.Guides.Get( - context.TODO(), - "guide_key", - knockmapi.GuideGetParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", guide.TargetAudienceID) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + guide, err := client.Guides.Get( + context.TODO(), + "guide_key", + knockmapi.GuideGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", guide.TargetAudienceID) } put: callbacks: {} @@ -8428,6 +10551,14 @@ paths: type: boolean x-struct: null x-validate: null + - description: When set to true, forces the upsert to override existing content regardless of environment restrictions. This bypasses the development-only environment check and origin environment checks. + in: query + name: force + required: false + schema: + type: boolean + x-struct: null + x-validate: null - description: Whether to commit the resource at the same time as modifying it. in: query name: commit @@ -8584,38 +10715,38 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Guides.Upsert( - context.TODO(), - "guide_key", - knockmapi.GuideUpsertParams{ - Environment: "development", - Guide: knockmapi.GuideUpsertParamsGuide{ - ChannelKey: "in-app-guide", - Name: "Getting Started Guide", - Steps: []knockmapi.GuideStepParam{knockmapi.GuideStepParam{ - Ref: "welcome-step", - SchemaKey: "tooltip", - SchemaSemver: "1.0.0", - SchemaVariantKey: "default", - }}, - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Guide) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Guides.Upsert( + context.TODO(), + "guide_key", + knockmapi.GuideUpsertParams{ + Environment: "development", + Guide: knockmapi.GuideUpsertParamsGuide{ + ChannelKey: "in-app-guide", + Name: "Getting Started Guide", + Steps: []knockmapi.GuideStepParam{{ + Ref: "welcome-step", + SchemaKey: "tooltip", + SchemaSemver: "1.0.0", + SchemaVariantKey: "default", + }}, + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Guide) } /v1/broadcasts/{broadcast_key}/send: put: @@ -8732,28 +10863,28 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Broadcasts.Send( - context.TODO(), - "broadcast_key", - knockmapi.BroadcastSendParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Broadcast) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Broadcasts.Send( + context.TODO(), + "broadcast_key", + knockmapi.BroadcastSendParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Broadcast) } /v1/guides: get: @@ -8911,24 +11042,24 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Guides.List(context.TODO(), knockmapi.GuideListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Guides.List(context.TODO(), knockmapi.GuideListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/broadcasts/{broadcast_key}/validate: put: @@ -9094,40 +11225,107 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Broadcasts.Validate( - context.TODO(), - "broadcast_key", - knockmapi.BroadcastValidateParams{ - Environment: "development", - Broadcast: knockmapi.BroadcastRequestParam{ - Name: "My Broadcast", - Steps: []knockmapi.BroadcastRequestStepUnionParam{knockmapi.BroadcastRequestStepUnionParam{ - OfWorkflowInAppFeedStep: &knockmapi.WorkflowInAppFeedStepParam{ - Ref: "channel_1", - Template: knockmapi.InAppFeedTemplateParam{ - MarkdownBody: "Hello **{{ recipient.name }}**", - }, - Type: knockmapi.WorkflowInAppFeedStepTypeChannel, - }, - }}, - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Broadcast) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Broadcasts.Validate( + context.TODO(), + "broadcast_key", + knockmapi.BroadcastValidateParams{ + Environment: "development", + Broadcast: knockmapi.BroadcastRequestParam{ + Name: "My Broadcast", + Steps: []knockmapi.BroadcastRequestStepUnionParam{{ + OfWorkflowInAppFeedStep: &knockmapi.WorkflowInAppFeedStepParam{ + Ref: "channel_1", + Template: knockmapi.InAppFeedTemplateParam{ + MarkdownBody: "Hello **{{ recipient.name }}**", + }, + Type: knockmapi.WorkflowInAppFeedStepTypeChannel, + }, + }}, + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Broadcast) + } + /v1/variables/{key}: + get: + callbacks: {} + description: Returns a single variable by key with per-environment value overrides. + operationId: getVariable + parameters: + - description: The key of the variable to retrieve. + in: path + name: key + required: true + schema: + type: string + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Variable' + description: OK + summary: Get a variable + tags: + - Variables + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const variable = await client.variables.retrieve('key'); + + console.log(variable.inserted_at); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + variable = client.variables.retrieve( + "key", + ) + print(variable.inserted_at) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + variable, err := client.Variables.Get(context.TODO(), "key") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", variable.InsertedAt) } /v1/translations: get: @@ -9152,6 +11350,14 @@ paths: type: string x-struct: null x-validate: null + - description: A specific tenant to filter translations for. + in: query + name: tenant + required: false + schema: + type: string + x-struct: null + x-validate: null - description: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. in: query name: format @@ -9289,24 +11495,24 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Translations.List(context.TODO(), knockmapi.TranslationListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Translations.List(context.TODO(), knockmapi.TranslationListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/workflows/{workflow_key}/steps/{step_ref}/preview_template: post: @@ -9398,50 +11604,43 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Workflows.Steps.PreviewTemplate( - context.TODO(), - "step_ref", - knockmapi.WorkflowStepPreviewTemplateParams{ - WorkflowKey: "workflow_key", - Environment: "development", - Recipient: knockmapi.WorkflowStepPreviewTemplateParamsRecipientUnion{ - OfString: knockmapi.String("dnedry"), - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.ContentType) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Workflows.Steps.PreviewTemplate( + context.TODO(), + "step_ref", + knockmapi.WorkflowStepPreviewTemplateParams{ + WorkflowKey: "workflow_key", + Environment: "development", + Recipient: knockmapi.WorkflowStepPreviewTemplateParamsRecipientUnion{ + OfString: knockmapi.String("dnedry"), + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.ContentType) } - /v1/guides/{guide_key}/activate: - put: + /v1/templates/preview: + post: callbacks: {} description: | - Activates (or deactivates) a guide in a given environment. You can either set the active status immediately or schedule it. + Renders a template preview, without requiring a template to be persisted within Knock. + This is useful for previewing templates in isolation, without the need to use a workflow. - Note: This immediately enables or disables a guide in a given environment without needing to go through environment promotion. - operationId: activateGuide + For email templates, you can optionally specify a layout by key or provide inline layout content. + operationId: previewTemplate parameters: - - description: The key of the guide. - in: path - name: guide_key - required: true - schema: - type: string - x-struct: null - x-validate: null - description: The environment slug. in: query name: environment @@ -9464,7 +11663,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GuideActivationParams' + $ref: '#/components/schemas/PreviewTemplateRequest' description: Params required: false responses: @@ -9472,47 +11671,168 @@ paths: content: application/json: schema: - description: Wraps the Guide response under the `guide` key. - example: - guide: - activation_url_patterns: - - directive: allow - pathname: /dashboard/* - active: true - archived_at: null - channel_key: in-app-guide - created_at: "2024-01-01T00:00:00Z" - description: A guide to help users get started with the application - environment: development - key: getting-started - name: Getting Started Guide - semver: 0.0.1 - sha: "1234567890" - steps: - - name: Welcome to the App - ref: welcome-step - schema_key: tooltip - schema_semver: 1.0.0 - schema_variant_key: default - values: - text_field: value - target_audience_id: null - target_property_conditions: - all: - - argument: some_property - operator: equal_to - variable: recipient.property - type: banner - updated_at: "2024-01-01T00:00:00Z" - valid: true - properties: - guide: - $ref: '#/components/schemas/Guide' - required: - - guide - title: WrappedGuideResponse - type: object - x-struct: null + $ref: '#/components/schemas/PreviewTemplateResponse' + description: OK + summary: Preview a template + tags: + - Templates + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const response = await client.templates.preview({ + environment: 'development', + channel_type: 'email', + recipient: 'user_123', + template: { + settings: {}, + subject: 'Hello {{ recipient.name }}', + }, + }); + + console.log(response.content_type); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + response = client.templates.preview( + environment="development", + channel_type="email", + recipient="user_123", + template={ + "settings": {}, + "subject": "Hello {{ recipient.name }}", + }, + ) + print(response.content_type) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Templates.Preview(context.TODO(), knockmapi.TemplatePreviewParams{ + Environment: "development", + ChannelType: knockmapi.TemplatePreviewParamsChannelTypeEmail, + Recipient: knockmapi.TemplatePreviewParamsRecipientUnion{ + OfString: knockmapi.String("user_123"), + }, + Template: knockmapi.TemplatePreviewParamsTemplateUnion{ + OfEmailTemplate: &knockmapi.EmailTemplateParam{ + Settings: knockmapi.EmailTemplateSettingsParam{}, + Subject: "Hello {{ recipient.name }}", + }, + }, + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.ContentType) + } + /v1/guides/{guide_key}/activate: + put: + callbacks: {} + description: | + Activates (or deactivates) a guide in a given environment. You can either set the active status immediately or schedule it. + + Note: This immediately enables or disables a guide in a given environment without needing to go through environment promotion. + operationId: activateGuide + parameters: + - description: The key of the guide. + in: path + name: guide_key + required: true + schema: + type: string + x-struct: null + x-validate: null + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + in: query + name: branch + required: false + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GuideActivationParams' + description: Params + required: false + responses: + "200": + content: + application/json: + schema: + description: Wraps the Guide response under the `guide` key. + example: + guide: + activation_url_patterns: + - directive: allow + pathname: /dashboard/* + active: true + archived_at: null + channel_key: in-app-guide + created_at: "2024-01-01T00:00:00Z" + description: A guide to help users get started with the application + environment: development + key: getting-started + name: Getting Started Guide + semver: 0.0.1 + sha: "1234567890" + steps: + - name: Welcome to the App + ref: welcome-step + schema_key: tooltip + schema_semver: 1.0.0 + schema_variant_key: default + values: + text_field: value + target_audience_id: null + target_property_conditions: + all: + - argument: some_property + operator: equal_to + variable: recipient.property + type: banner + updated_at: "2024-01-01T00:00:00Z" + valid: true + properties: + guide: + $ref: '#/components/schemas/Guide' + required: + - guide + title: WrappedGuideResponse + type: object + x-struct: null x-validate: null description: OK summary: Activate a guide @@ -9549,28 +11869,28 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Guides.Activate( - context.TODO(), - "guide_key", - knockmapi.GuideActivateParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Guide) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Guides.Activate( + context.TODO(), + "guide_key", + knockmapi.GuideActivateParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Guide) } /v1/partials: get: @@ -9718,24 +12038,24 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Partials.List(context.TODO(), knockmapi.PartialListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Partials.List(context.TODO(), knockmapi.PartialListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/partials/{partial_key}: get: @@ -9822,28 +12142,28 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - partial, err := client.Partials.Get( - context.TODO(), - "partial_key", - knockmapi.PartialGetParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", partial.Valid) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + partial, err := client.Partials.Get( + context.TODO(), + "partial_key", + knockmapi.PartialGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", partial.Valid) } put: callbacks: {} @@ -9887,6 +12207,14 @@ paths: type: boolean x-struct: null x-validate: null + - description: When set to true, forces the upsert to override existing content regardless of environment restrictions. This bypasses the development-only environment check and origin environment checks. + in: query + name: force + required: false + schema: + type: boolean + x-struct: null + x-validate: null - description: Whether to commit the resource at the same time as modifying it. in: query name: commit @@ -9911,8 +12239,19 @@ paths: example: partial: content:

      Hello, world!

      + description: This is a test partial + input_schema: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text name: My Partial type: html + visual_block_enabled: true properties: partial: $ref: '#/components/schemas/PartialRequest' @@ -10004,33 +12343,33 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Partials.Upsert( - context.TODO(), - "partial_key", - knockmapi.PartialUpsertParams{ - Environment: "development", - Partial: knockmapi.PartialUpsertParamsPartial{ - Content: "

      Hello, world!

      ", - Name: "My Partial", - Type: "html", - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Partial) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Partials.Upsert( + context.TODO(), + "partial_key", + knockmapi.PartialUpsertParams{ + Environment: "development", + Partial: knockmapi.PartialUpsertParamsPartial{ + Content: "

      Hello, world!

      ", + Name: "My Partial", + Type: "html", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Partial) } /v1/channels: get: @@ -10047,6 +12386,20 @@ paths: type: string x-struct: null x-validate: null + - description: Associated resources to include in the response. Accepts `environment_settings` to include per-environment channel configuration. + in: query + name: include + required: false + schema: + items: + enum: + - environment_settings + type: string + x-struct: null + x-validate: null + type: array + x-struct: null + x-validate: null - description: The cursor to fetch entries after. in: query name: after @@ -10140,24 +12493,148 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Channels.List(context.TODO(), knockmapi.ChannelListParams{}) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) + } + /v1/members/{id}: + delete: + callbacks: {} + description: Removes a member from the account. + operationId: removeMember + parameters: + - description: The ID of the member to remove. + in: path + name: id + required: true + schema: + format: uuid + type: string + x-struct: null + x-validate: null + responses: + "204": + description: No Content + summary: Remove a member + tags: + - Members + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + await client.members.delete('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + client.members.delete( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + go: | + package main + + import ( + "context" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + err := client.Members.Delete(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + if err != nil { + panic(err.Error()) + } + } + get: + callbacks: {} + description: Returns a single member by their ID. + operationId: getMember + parameters: + - description: The ID of the member to retrieve. + in: path + name: id + required: true + schema: + format: uuid + type: string + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Member' + description: OK + summary: Get a member + tags: + - Members + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const member = await client.members.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); + + console.log(member.id); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + member = client.members.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + print(member.id) + go: | + package main + + import ( + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Channels.List(context.TODO(), knockmapi.ChannelListParams{ - - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + member, err := client.Members.Get(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", member.ID) } /v1/email_layouts/{email_layout_key}: get: @@ -10229,7 +12706,7 @@ paths: environment: 'development', }); - console.log(emailLayout.created_at); + console.log(emailLayout.branding_overrides); python: |- import os from knock_mapi import KnockMgmt @@ -10241,33 +12718,33 @@ paths: email_layout_key="email_layout_key", environment="development", ) - print(email_layout.created_at) + print(email_layout.branding_overrides) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - emailLayout, err := client.EmailLayouts.Get( - context.TODO(), - "email_layout_key", - knockmapi.EmailLayoutGetParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", emailLayout.CreatedAt) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + emailLayout, err := client.EmailLayouts.Get( + context.TODO(), + "email_layout_key", + knockmapi.EmailLayoutGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", emailLayout.BrandingOverrides) } put: callbacks: {} @@ -10311,6 +12788,14 @@ paths: type: boolean x-struct: null x-validate: null + - description: When set to true, forces the upsert to override existing content regardless of environment restrictions. This bypasses the development-only environment check and origin environment checks. + in: query + name: force + required: false + schema: + type: boolean + x-struct: null + x-validate: null - description: Whether to commit the resource at the same time as modifying it. in: query name: commit @@ -10334,6 +12819,12 @@ paths: description: Wraps the EmailLayoutRequest request under the email_layout key. example: email_layout: + branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' footer_links: - text: Example url: http://example.com @@ -10359,6 +12850,12 @@ paths: description: Wraps the EmailLayout response under the `email_layout` key. example: email_layout: + branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' created_at: "2021-01-01T00:00:00Z" environment: development footer_links: @@ -10422,33 +12919,33 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.EmailLayouts.Upsert( - context.TODO(), - "email_layout_key", - knockmapi.EmailLayoutUpsertParams{ - Environment: "development", - EmailLayout: knockmapi.EmailLayoutUpsertParamsEmailLayout{ - HTMLLayout: "Hello, world!", - Name: "Transactional", - TextLayout: "Hello, world!", - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.EmailLayout) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.EmailLayouts.Upsert( + context.TODO(), + "email_layout_key", + knockmapi.EmailLayoutUpsertParams{ + Environment: "development", + EmailLayout: knockmapi.EmailLayoutUpsertParamsEmailLayout{ + HTMLLayout: "Hello, world!", + Name: "Transactional", + TextLayout: "Hello, world!", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.EmailLayout) } /v1/channel_groups: get: @@ -10562,24 +13059,22 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.ChannelGroups.List(context.TODO(), knockmapi.ChannelGroupListParams{ - - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.ChannelGroups.List(context.TODO(), knockmapi.ChannelGroupListParams{}) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/workflows: get: @@ -10759,24 +13254,24 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Workflows.List(context.TODO(), knockmapi.WorkflowListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Workflows.List(context.TODO(), knockmapi.WorkflowListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/broadcasts: get: @@ -10931,24 +13426,24 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Broadcasts.List(context.TODO(), knockmapi.BroadcastListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Broadcasts.List(context.TODO(), knockmapi.BroadcastListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/translations/{locale_code}/validate: put: @@ -11066,32 +13561,32 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Translations.Validate( - context.TODO(), - "locale_code", - knockmapi.TranslationValidateParams{ - Environment: "development", - Translation: knockmapi.TranslationValidateParamsTranslation{ - Content: `{"hello":"Hello, world!"}`, - Format: "json", - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Translation) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Translations.Validate( + context.TODO(), + "locale_code", + knockmapi.TranslationValidateParams{ + Environment: "development", + Translation: knockmapi.TranslationValidateParamsTranslation{ + Content: `{"hello":"Hello, world!"}`, + Format: "json", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Translation) } /v1/whoami: get: @@ -11133,48 +13628,71 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Auth.Verify(context.TODO()) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.UserID) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Auth.Verify(context.TODO()) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.UserID) } - /v1/commits/{id}: - get: + /v1/audiences/{audience_key}: + delete: callbacks: {} - description: Retrieve a single commit by its ID. - operationId: getCommit + description: Archives a given audience across all environments. + operationId: archiveAudience parameters: - - description: The id of the commit to retrieve. + - description: The key of the audience to archive. in: path - name: id + name: audience_key required: true schema: - format: uuid type: string x-struct: null x-validate: null - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/Commit' - description: OK - summary: Get a commit + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + description: The response from archiving an audience. + example: + result: success + properties: + result: + description: The result of the archive operation. + example: success + type: string + x-struct: null + x-validate: null + required: + - result + title: ArchiveAudienceResponse + type: object + x-struct: null + x-validate: null + description: OK + summary: Archive an audience tags: - - Commits + - Audiences x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -11183,9 +13701,9 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const commit = await client.commits.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); + const response = await client.audiences.archive('audience_key', { environment: 'development' }); - console.log(commit.id); + console.log(response.result); python: |- import os from knock_mapi import KnockMgmt @@ -11193,37 +13711,51 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - commit = client.commits.retrieve( - "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + response = client.audiences.archive( + audience_key="audience_key", + environment="development", ) - print(commit.id) + print(response.result) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - commit, err := client.Commits.Get(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", commit.ID) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Audiences.Archive( + context.TODO(), + "audience_key", + knockmapi.AudienceArchiveParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Result) } - /v1/variables: get: callbacks: {} - description: Returns a paginated list of variables for a given environment. - operationId: listVariables + description: Retrieve an audience by its key in a given environment. + operationId: getAudience parameters: + - description: The key of the audience to retrieve. + in: path + name: audience_key + required: true + schema: + type: string + x-struct: null + x-validate: null - description: The environment slug. in: query name: environment @@ -11242,28 +13774,20 @@ paths: type: string x-struct: null x-validate: null - - description: The cursor to fetch entries after. - in: query - name: after - required: false - schema: - type: string - x-struct: null - x-validate: null - - description: The cursor to fetch entries before. + - description: Whether to annotate the resource. Only used in the Knock CLI. in: query - name: before + name: annotate required: false schema: - type: string + type: boolean x-struct: null x-validate: null - - description: The number of entries to fetch per-page. + - description: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. in: query - name: limit + name: hide_uncommitted_changes required: false schema: - type: integer + type: boolean x-struct: null x-validate: null responses: @@ -11271,41 +13795,11 @@ paths: content: application/json: schema: - description: A paginated list of Variable. Contains a list of entries and page information. - example: - entries: - - description: This is a description of my variable. - inserted_at: "2021-01-01T00:00:00Z" - key: my_variable - type: public - updated_at: "2021-01-01T00:00:00Z" - value: my_value - page_info: - after: null - before: null - page_size: 25 - properties: - entries: - description: A list of entries. - items: - $ref: '#/components/schemas/Variable' - nullable: false - type: array - x-struct: null - x-validate: null - page_info: - $ref: '#/components/schemas/PageInfo' - required: - - entries - - page_info - title: PaginatedVariableResponse - type: object - x-struct: null - x-validate: null + $ref: '#/components/schemas/Audience' description: OK - summary: List variables + summary: Get an audience tags: - - Variables + - Audiences x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -11314,10 +13808,9 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - // Automatically fetches more pages as needed. - for await (const variable of client.variables.list({ environment: 'development' })) { - console.log(variable.inserted_at); - } + const audience = await client.audiences.retrieve('audience_key', { environment: 'development' }); + + console.log(audience); python: |- import os from knock_mapi import KnockMgmt @@ -11325,51 +13818,43 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - page = client.variables.list( + audience = client.audiences.retrieve( + audience_key="audience_key", environment="development", ) - page = page.entries[0] - print(page.inserted_at) + print(audience) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Variables.List(context.TODO(), knockmapi.VariableListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + audience, err := client.Audiences.Get( + context.TODO(), + "audience_key", + knockmapi.AudienceGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", audience) } - /v1/workflows/{workflow_key}/activate: put: callbacks: {} - description: | - Activates (or deactivates) a workflow in a given environment. Read more in the [docs](https://docs.knock.app/concepts/workflows#workflow-status). - - Note: This immediately enables or disables a workflow in a given environment without needing to go through environment promotion. - operationId: activateWorkflow + description: Updates an audience of a given key, or creates a new one if it does not yet exist. + operationId: upsertAudience parameters: - - description: The key of the workflow. - in: path - name: workflow_key - required: true - schema: - type: string - x-struct: null - x-validate: null - description: The environment slug. in: query name: environment @@ -11388,21 +13873,67 @@ paths: type: string x-struct: null x-validate: null + - description: The key of the audience to upsert. + in: path + name: audience_key + required: true + schema: + type: string + x-struct: null + x-validate: null + - description: Whether to annotate the resource. Only used in the Knock CLI. + in: query + name: annotate + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: When set to true, forces the upsert to override existing content regardless of environment restrictions. This bypasses the development-only environment check and origin environment checks. + in: query + name: force + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: Whether to commit the resource at the same time as modifying it. + in: query + name: commit + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: The message to commit the resource with, only used if `commit` is `true`. + in: query + name: commit_message + required: false + schema: + type: string + x-struct: null + x-validate: null requestBody: content: application/json: schema: + description: Wraps the AudienceRequest request under the audience key. example: - status: true + audience: + description: Users on the premium plan + name: Premium users + segments: + - conditions: + - argument: premium + operator: equal_to + property: recipient.plan + type: dynamic properties: - status: - description: Whether to activate or deactivate the workflow. Set to `true` by default, which will activate the workflow. - example: true - type: boolean - x-struct: null - x-validate: null + audience: + $ref: '#/components/schemas/AudienceRequest' required: - - status + - audience + title: WrappedAudienceRequestRequest type: object x-struct: null x-validate: null @@ -11413,74 +13944,35 @@ paths: content: application/json: schema: - description: Wraps the Workflow response under the `workflow` key. + description: Wraps the Audience response under the `audience` key. example: - workflow: - active: false - categories: - - marketing - - black-friday - conditions: - all: - - argument: admin - operator: equal_to - variable: recipient.role - created_at: "2022-12-16T19:07:50.027113Z" - description: This is a dummy workflow for demo purposes. + audience: + created_at: "2024-01-15T10:30:00Z" + description: Customers who signed up in the last 30 days. environment: development - key: december-16-demo - name: december-16-demo - settings: - override_preferences: true - sha: f7e9d3b2a1c8e6m4k5j7h9g0i2l3n4p6q8r0t1u3v5w7x9y - steps: - - channel_key: in-app-feed - channel_type: in_app_feed - description: Main in-app feed - name: In-app step - ref: in_app_feed_1 - template: - action_url: '{{ data.onboarding_url }}' - markdown_body: Hello **{{ recipient.name }}**. Click here to get started. - type: channel - - ref: delay_1 - settings: - delay_for: - unit: hours - value: 1 - type: delay - - channel_key: postmark - channel_type: email - ref: email_1 - template: - html_body:

      Hello, {{ recipient.name }}! Welcome to {{ vars.app_name }} Get started here.

      - settings: - layout_key: default - subject: Welcome to {{ vars.app_name }}! - type: channel - trigger_data_json_schema: - properties: - onboarding_url: - type: string - required: - - onboarding_url - type: object - trigger_frequency: every_trigger - updated_at: "2023-02-08T22:15:19.846681Z" - valid: true + key: new-customers + name: New customers + segments: + - conditions: + - argument: 30_days_ago + operator: greater_than + property: recipient.created_at + sha: a1b2c3d4e5f6 + type: dynamic + updated_at: "2024-06-20T14:45:00Z" properties: - workflow: - $ref: '#/components/schemas/Workflow' + audience: + $ref: '#/components/schemas/Audience' required: - - workflow - title: WrappedWorkflowResponse + - audience + title: WrappedAudienceResponse type: object x-struct: null x-validate: null description: OK - summary: Activate a workflow + summary: Upsert an audience tags: - - Workflows + - Audiences x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -11489,12 +13981,12 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.workflows.activate('workflow_key', { + const response = await client.audiences.upsert('audience_key', { environment: 'development', - status: true, + audience: { name: 'Premium users', type: 'dynamic' }, }); - console.log(response.workflow); + console.log(response.audience); python: |- import os from knock_mapi import KnockMgmt @@ -11502,137 +13994,72 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.workflows.activate( - workflow_key="workflow_key", + response = client.audiences.upsert( + audience_key="audience_key", environment="development", - status=True, + audience={ + "name": "Premium users", + "type": "dynamic", + }, ) - print(response.workflow) + print(response.audience) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Workflows.Activate( - context.TODO(), - "workflow_key", - knockmapi.WorkflowActivateParams{ - Environment: "development", - Status: true, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Workflow) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Audiences.Upsert( + context.TODO(), + "audience_key", + knockmapi.AudienceUpsertParams{ + Environment: "development", + Audience: knockmapi.AudienceUpsertParamsAudienceUnion{ + OfDynamic: &knockmapi.AudienceUpsertParamsAudienceDynamic{ + Name: "Premium users", + }, + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Audience) } - /v1/partials/{partial_key}/validate: - put: + /v1/commits/{id}: + get: callbacks: {} - description: | - Validates a partial payload without persisting it. - - Note: this endpoint only operates on partials in the β€œdevelopment” environment. - operationId: validatePartial + description: Retrieve a single commit by its ID. + operationId: getCommit parameters: - - description: The environment slug. - in: query - name: environment + - description: The id of the commit to retrieve. + in: path + name: id required: true schema: - example: development + format: uuid type: string x-struct: null x-validate: null - - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. - in: query - name: branch - required: false - schema: - example: feature-branch - type: string - x-struct: null - x-validate: null - - description: The key of the partial to validate. - in: path - name: partial_key - required: true - schema: - type: string - x-struct: null - x-validate: null - requestBody: - content: - application/json: - schema: - description: Wraps the PartialRequest request under the partial key. - example: - partial: - content:

      Hello, world!

      - name: My Partial - type: html - properties: - partial: - $ref: '#/components/schemas/PartialRequest' - required: - - partial - title: WrappedPartialRequestRequest - type: object - x-struct: null - x-validate: null - description: Params - required: false responses: "200": content: application/json: schema: - description: Wraps the Partial response under the `partial` key. - example: - partial: - content:

      Hello, world!

      - description: This is a test partial - environment: development - icon_name: icon-name - input_schema: - - key: text_field - label: My text field - settings: - description: A description of the text field - max_length: 100 - min_length: 10 - required: true - type: text - inserted_at: "2021-01-01T00:00:00Z" - key: my-partial - name: My Partial - type: html - updated_at: "2021-01-01T00:00:00Z" - valid: true - visual_block_enabled: true - properties: - partial: - $ref: '#/components/schemas/Partial' - required: - - partial - title: WrappedPartialResponse - type: object - x-struct: null - x-validate: null + $ref: '#/components/schemas/Commit' description: OK - summary: Validate a partial + summary: Get a commit tags: - - Partials + - Commits x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -11641,16 +14068,9 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.partials.validate('partial_key', { - environment: 'development', - partial: { - content: '

      Hello, world!

      ', - name: 'My Partial', - type: 'html', - }, - }); + const commit = await client.commits.retrieve('182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e'); - console.log(response.partial); + console.log(commit.id); python: |- import os from knock_mapi import KnockMgmt @@ -11658,66 +14078,37 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.partials.validate( - partial_key="partial_key", - environment="development", - partial={ - "content": "

      Hello, world!

      ", - "name": "My Partial", - "type": "html", - }, + commit = client.commits.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) - print(response.partial) + print(commit.id) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Partials.Validate( - context.TODO(), - "partial_key", - knockmapi.PartialValidateParams{ - Environment: "development", - Partial: knockmapi.PartialValidateParamsPartial{ - Content: "

      Hello, world!

      ", - Name: "My Partial", - Type: "html", - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Partial) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + commit, err := client.Commits.Get(context.TODO(), "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", commit.ID) } - /v1/message_types/{message_type_key}/validate: + /v1/audiences/{audience_key}/validate: put: callbacks: {} - description: | - Validates a message type payload without persisting it. - - Note: this endpoint only operates on message types in the `development` environment. - operationId: validateMessageType + description: Validates an audience payload without persisting it. + operationId: validateAudience parameters: - - description: The key of the message type to validate. - in: path - name: message_type_key - required: true - schema: - example: email - type: string - x-struct: null - x-validate: null - description: The environment slug. in: query name: environment @@ -11736,34 +14127,35 @@ paths: type: string x-struct: null x-validate: null + - description: The key of the audience to validate. + in: path + name: audience_key + required: true + schema: + type: string + x-struct: null + x-validate: null requestBody: content: application/json: schema: - description: Wraps the MessageTypeRequest request under the message_type key. + description: Wraps the AudienceRequest request under the audience key. example: - message_type: - description: This is a message type - name: My Message Type - preview:
      Hello, world!
      - variants: - - fields: - - key: text_field - label: My text field - settings: - description: A description of the text field - max_length: 100 - min_length: 10 - required: true - type: text - key: default - name: Default + audience: + description: Users on the premium plan + name: Premium users + segments: + - conditions: + - argument: premium + operator: equal_to + property: recipient.plan + type: dynamic properties: - message_type: - $ref: '#/components/schemas/MessageTypeRequest' + audience: + $ref: '#/components/schemas/AudienceRequest' required: - - message_type - title: WrappedMessageTypeRequestRequest + - audience + title: WrappedAudienceRequestRequest type: object x-struct: null x-validate: null @@ -11774,48 +14166,35 @@ paths: content: application/json: schema: - description: Wraps the MessageType response under the `message_type` key. + description: Wraps the Audience response under the `audience` key. example: - message_type: - archived_at: null - created_at: "2021-01-01T00:00:00Z" - deleted_at: null - description: Email message type + audience: + created_at: "2024-01-15T10:30:00Z" + description: Customers who signed up in the last 30 days. environment: development - icon_name: email - key: email - name: Email - owner: user - preview:
      Hello, world!
      - semver: 1.0.0 - sha: "1234567890" - updated_at: "2021-01-01T00:00:00Z" - valid: true - variants: - - fields: - - key: text_field - label: My text field - settings: - description: A description of the text field - max_length: 100 - min_length: 10 - required: true - type: text - key: default - name: Default + key: new-customers + name: New customers + segments: + - conditions: + - argument: 30_days_ago + operator: greater_than + property: recipient.created_at + sha: a1b2c3d4e5f6 + type: dynamic + updated_at: "2024-06-20T14:45:00Z" properties: - message_type: - $ref: '#/components/schemas/MessageType' + audience: + $ref: '#/components/schemas/Audience' required: - - message_type - title: WrappedMessageTypeResponse + - audience + title: WrappedAudienceResponse type: object x-struct: null x-validate: null description: OK - summary: Validate message type + summary: Validate an audience tags: - - Message types + - Audiences x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -11824,16 +14203,12 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.messageTypes.validate('email', { + const response = await client.audiences.validate('audience_key', { environment: 'development', - message_type: { - description: 'This is a message type', - name: 'My Message Type', - preview: '
      Hello, world!
      ', - }, + audience: { name: 'Premium users', type: 'dynamic' }, }); - console.log(response.message_type); + console.log(response.audience); python: |- import os from knock_mapi import KnockMgmt @@ -11841,62 +14216,53 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.message_types.validate( - message_type_key="email", + response = client.audiences.validate( + audience_key="audience_key", environment="development", - message_type={ - "description": "This is a message type", - "name": "My Message Type", - "preview": "
      Hello, world!
      ", + audience={ + "name": "Premium users", + "type": "dynamic", }, ) - print(response.message_type) + print(response.audience) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.MessageTypes.Validate( - context.TODO(), - "email", - knockmapi.MessageTypeValidateParams{ - Environment: "development", - MessageType: knockmapi.MessageTypeValidateParamsMessageType{ - Description: knockmapi.String("This is a message type"), - Name: "My Message Type", - Preview: "
      Hello, world!
      ", - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.MessageType) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Audiences.Validate( + context.TODO(), + "audience_key", + knockmapi.AudienceValidateParams{ + Environment: "development", + Audience: knockmapi.AudienceValidateParamsAudienceUnion{ + OfDynamic: &knockmapi.AudienceValidateParamsAudienceDynamic{ + Name: "Premium users", + }, + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Audience) } - /v1/translations/{locale_code}: + /v1/variables: get: callbacks: {} - description: Retrieve a translation by its locale and namespace, in a given environment. - operationId: getTranslation + description: Returns a list of variables. When an environment is specified, returns per-environment variables. Otherwise, returns project-scoped variables with per-environment overrides. + operationId: listVariables parameters: - - description: A specific locale code to filter translations for. - in: path - name: locale_code - required: true - schema: - type: string - x-struct: null - x-validate: null - description: The environment slug. in: query name: environment @@ -11915,39 +14281,39 @@ paths: type: string x-struct: null x-validate: null - - description: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. + - description: Filter variables by type. Supports 'public' or 'secret'. in: query - name: format + name: type required: false schema: enum: - - json - - po + - public + - secret type: string x-struct: null x-validate: null - - description: A specific namespace to filter translations for. + - description: The cursor to fetch entries after. in: query - name: namespace + name: after required: false schema: type: string x-struct: null x-validate: null - - description: Whether to annotate the resource. Only used in the Knock CLI. + - description: The cursor to fetch entries before. in: query - name: annotate + name: before required: false schema: - type: boolean + type: string x-struct: null x-validate: null - - description: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. + - description: The number of entries to fetch per-page. in: query - name: hide_uncommitted_changes + name: limit required: false schema: - type: boolean + type: integer x-struct: null x-validate: null responses: @@ -11955,28 +14321,44 @@ paths: content: application/json: schema: - description: Wraps the Translation response under the `translation` key. + description: A paginated list of Variable. Contains a list of entries and page information. example: - translation: - content: '{"hello":"Hello, world!"}' - format: json - inserted_at: "2021-01-01T00:00:00Z" - locale_code: en - namespace: my_app - updated_at: "2021-01-01T00:00:00Z" + entries: + - description: This is a description of my variable. + environment_values: + development: dev_value + production: prod_value + inserted_at: "2021-01-01T00:00:00Z" + key: my_variable + type: public + updated_at: "2021-01-01T00:00:00Z" + value: my_value + page_info: + after: null + before: null + page_size: 25 properties: - translation: - $ref: '#/components/schemas/Translation' - required: - - translation - title: WrappedTranslationResponse - type: object - x-struct: null - x-validate: null - description: OK - summary: Get translation + entries: + description: A list of entries. + items: + $ref: '#/components/schemas/Variable' + nullable: false + type: array + x-struct: null + x-validate: null + page_info: + $ref: '#/components/schemas/PageInfo' + required: + - entries + - page_info + title: PaginatedVariableResponse + type: object + x-struct: null + x-validate: null + description: OK + summary: List variables tags: - - Translations + - Variables x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -11985,11 +14367,10 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const translation = await client.translations.retrieve('locale_code', { - environment: 'development', - }); - - console.log(translation.translation); + // Automatically fetches more pages as needed. + for await (const variable of client.variables.list({ environment: 'development' })) { + console.log(variable.inserted_at); + } python: |- import os from knock_mapi import KnockMgmt @@ -11997,49 +14378,46 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - translation = client.translations.retrieve( - locale_code="locale_code", + page = client.variables.list( environment="development", ) - print(translation.translation) + page = page.entries[0] + print(page.inserted_at) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - translation, err := client.Translations.Get( - context.TODO(), - "locale_code", - knockmapi.TranslationGetParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", translation.Translation) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Variables.List(context.TODO(), knockmapi.VariableListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } + /v1/workflows/{workflow_key}/activate: put: callbacks: {} description: | - Updates a translation of a given locale code + namespace, or creates a new one if it does not yet exist. + Activates (or deactivates) a workflow in a given environment. Read more in the [docs](https://docs.knock.app/concepts/workflows#workflow-status). - Note: this endpoint only operates on translations in the "development" environment. - operationId: upsertTranslation + Note: This immediately enables or disables a workflow in a given environment without needing to go through environment promotion. + operationId: activateWorkflow parameters: - - description: A locale code of the translation. + - description: The key of the workflow. in: path - name: locale_code + name: workflow_key required: true schema: type: string @@ -12063,64 +14441,21 @@ paths: type: string x-struct: null x-validate: null - - description: An optional namespace that identifies the translation. - in: query - name: namespace - required: true - schema: - type: string - x-struct: null - x-validate: null - - description: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. - in: query - name: format - required: false - schema: - enum: - - json - - po - type: string - x-struct: null - x-validate: null - - description: Whether to annotate the resource. Only used in the Knock CLI. - in: query - name: annotate - required: false - schema: - type: boolean - x-struct: null - x-validate: null - - description: Whether to commit the resource at the same time as modifying it. - in: query - name: commit - required: false - schema: - type: boolean - x-struct: null - x-validate: null - - description: The message to commit the resource with, only used if `commit` is `true`. - in: query - name: commit_message - required: false - schema: - type: string - x-struct: null - x-validate: null requestBody: content: application/json: schema: - description: Wraps the TranslationRequest request under the translation key. example: - translation: - content: '{"hello":"Hello, world!"}' - format: json + status: true properties: - translation: - $ref: '#/components/schemas/TranslationRequest' + status: + description: Whether to activate or deactivate the workflow. Set to `true` by default, which will activate the workflow. + example: true + type: boolean + x-struct: null + x-validate: null required: - - translation - title: WrappedTranslationRequestRequest + - status type: object x-struct: null x-validate: null @@ -12131,28 +14466,74 @@ paths: content: application/json: schema: - description: Wraps the Translation response under the `translation` key. + description: Wraps the Workflow response under the `workflow` key. example: - translation: - content: '{"hello":"Hello, world!"}' - format: json - inserted_at: "2021-01-01T00:00:00Z" - locale_code: en - namespace: my_app - updated_at: "2021-01-01T00:00:00Z" + workflow: + active: false + categories: + - marketing + - black-friday + conditions: + all: + - argument: admin + operator: equal_to + variable: recipient.role + created_at: "2022-12-16T19:07:50.027113Z" + description: This is a dummy workflow for demo purposes. + environment: development + key: december-16-demo + name: december-16-demo + settings: + override_preferences: true + sha: f7e9d3b2a1c8e6m4k5j7h9g0i2l3n4p6q8r0t1u3v5w7x9y + steps: + - channel_key: in-app-feed + channel_type: in_app_feed + description: Main in-app feed + name: In-app step + ref: in_app_feed_1 + template: + action_url: '{{ data.onboarding_url }}' + markdown_body: Hello **{{ recipient.name }}**. Click here to get started. + type: channel + - ref: delay_1 + settings: + delay_for: + unit: hours + value: 1 + type: delay + - channel_key: postmark + channel_type: email + ref: email_1 + template: + html_body:

      Hello, {{ recipient.name }}! Welcome to {{ vars.app_name }} Get started here.

      + settings: + layout_key: default + subject: Welcome to {{ vars.app_name }}! + type: channel + trigger_data_json_schema: + properties: + onboarding_url: + type: string + required: + - onboarding_url + type: object + trigger_frequency: every_trigger + updated_at: "2023-02-08T22:15:19.846681Z" + valid: true properties: - translation: - $ref: '#/components/schemas/Translation' + workflow: + $ref: '#/components/schemas/Workflow' required: - - translation - title: WrappedTranslationResponse + - workflow + title: WrappedWorkflowResponse type: object x-struct: null x-validate: null description: OK - summary: Upsert translation + summary: Activate a workflow tags: - - Translations + - Workflows x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -12161,13 +14542,12 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.translations.upsert('locale_code', { + const response = await client.workflows.activate('workflow_key', { environment: 'development', - namespace: 'namespace', - translation: { content: '{"hello":"Hello, world!"}', format: 'json' }, + status: true, }); - console.log(response.translation); + console.log(response.workflow); python: |- import os from knock_mapi import KnockMgmt @@ -12175,122 +14555,148 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.translations.upsert( - locale_code="locale_code", + response = client.workflows.activate( + workflow_key="workflow_key", environment="development", - namespace="namespace", - translation={ - "content": "{\"hello\":\"Hello, world!\"}", - "format": "json", - }, + status=True, ) - print(response.translation) + print(response.workflow) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Translations.Upsert( - context.TODO(), - "locale_code", - knockmapi.TranslationUpsertParams{ - Environment: "development", - Namespace: "namespace", - Translation: knockmapi.TranslationUpsertParamsTranslation{ - Content: `{"hello":"Hello, world!"}`, - Format: "json", - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Translation) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Workflows.Activate( + context.TODO(), + "workflow_key", + knockmapi.WorkflowActivateParams{ + Environment: "development", + Status: true, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Workflow) } - /v1/environments: - get: + /v1/partials/{partial_key}/validate: + put: callbacks: {} - description: Returns a paginated list of environments. The environments will be returned in order of their index, with the `development` environment first. - operationId: listEnvironments + description: | + Validates a partial payload without persisting it. + + Note: this endpoint only operates on partials in the β€œdevelopment” environment. + operationId: validatePartial parameters: - - description: The cursor to fetch entries after. + - description: The environment slug. in: query - name: after - required: false + name: environment + required: true schema: + example: development type: string x-struct: null x-validate: null - - description: The cursor to fetch entries before. + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. in: query - name: before + name: branch required: false schema: + example: feature-branch type: string x-struct: null x-validate: null - - description: The number of entries to fetch per-page. - in: query - name: limit - required: false + - description: The key of the partial to validate. + in: path + name: partial_key + required: true schema: - type: integer + type: string x-struct: null x-validate: null - responses: - "200": - content: - application/json: - schema: - description: A paginated list of Environment. Contains a list of entries and page information. - example: - entries: - - created_at: "2022-10-31T19:59:03Z" - deleted_at: null - hide_pii_data: false - label_color: '#000000' - last_commit_at: "2022-10-31T19:59:03Z" - name: Development - order: 0 - owner: system - slug: development - updated_at: "2022-10-31T19:59:03Z" - page_info: - after: null - before: null - page_size: 25 + requestBody: + content: + application/json: + schema: + description: Wraps the PartialRequest request under the partial key. + example: + partial: + content:

      Hello, world!

      + description: This is a test partial + input_schema: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text + name: My Partial + type: html + visual_block_enabled: true + properties: + partial: + $ref: '#/components/schemas/PartialRequest' + required: + - partial + title: WrappedPartialRequestRequest + type: object + x-struct: null + x-validate: null + description: Params + required: false + responses: + "200": + content: + application/json: + schema: + description: Wraps the Partial response under the `partial` key. + example: + partial: + content:

      Hello, world!

      + description: This is a test partial + environment: development + icon_name: icon-name + input_schema: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text + inserted_at: "2021-01-01T00:00:00Z" + key: my-partial + name: My Partial + type: html + updated_at: "2021-01-01T00:00:00Z" + valid: true + visual_block_enabled: true properties: - entries: - description: A list of entries. - items: - $ref: '#/components/schemas/Environment' - nullable: false - type: array - x-struct: null - x-validate: null - page_info: - $ref: '#/components/schemas/PageInfo' + partial: + $ref: '#/components/schemas/Partial' required: - - entries - - page_info - title: PaginatedEnvironmentResponse + - partial + title: WrappedPartialResponse type: object x-struct: null x-validate: null description: OK - summary: List environments + summary: Validate a partial tags: - - Environments + - Partials x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -12299,10 +14705,16 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - // Automatically fetches more pages as needed. - for await (const environment of client.environments.list()) { - console.log(environment.hide_pii_data); - } + const response = await client.partials.validate('partial_key', { + environment: 'development', + partial: { + content: '

      Hello, world!

      ', + name: 'My Partial', + type: 'html', + }, + }); + + console.log(response.partial); python: |- import os from knock_mapi import KnockMgmt @@ -12310,46 +14722,63 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - page = client.environments.list() - page = page.entries[0] - print(page.hide_pii_data) + response = client.partials.validate( + partial_key="partial_key", + environment="development", + partial={ + "content": "

      Hello, world!

      ", + "name": "My Partial", + "type": "html", + }, + ) + print(response.partial) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Environments.List(context.TODO(), knockmapi.EnvironmentListParams{ - - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Partials.Validate( + context.TODO(), + "partial_key", + knockmapi.PartialValidateParams{ + Environment: "development", + Partial: knockmapi.PartialValidateParamsPartial{ + Content: "

      Hello, world!

      ", + Name: "My Partial", + Type: "html", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Partial) } - /v1/email_layouts/{email_layout_key}/validate: + /v1/message_types/{message_type_key}/validate: put: callbacks: {} description: | - Validates an email layout payload without persisting it. + Validates a message type payload without persisting it. - Note: this endpoint only operates in the "development" environment. - operationId: validateEmailLayout + Note: this endpoint only operates on message types in the `development` environment. + operationId: validateMessageType parameters: - - description: The key of the email layout to validate. + - description: The key of the message type to validate. in: path - name: email_layout_key + name: message_type_key required: true schema: + example: email type: string x-struct: null x-validate: null @@ -12375,58 +14804,82 @@ paths: content: application/json: schema: - description: Wraps the EmailLayoutRequest request under the email_layout key. + description: Wraps the MessageTypeRequest request under the message_type key. example: - email_layout: - footer_links: - - text: Example - url: http://example.com - html_layout: Hello, world! - name: Transactional - text_layout: Hello, world! + message_type: + description: This is a message type + name: My Message Type + preview:
      Hello, world!
      + variants: + - fields: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text + key: default + name: Default properties: - email_layout: - $ref: '#/components/schemas/EmailLayoutRequest' + message_type: + $ref: '#/components/schemas/MessageTypeRequest' required: - - email_layout - title: WrappedEmailLayoutRequestRequest + - message_type + title: WrappedMessageTypeRequestRequest type: object x-struct: null x-validate: null - description: Email layout + description: Params required: false responses: "200": content: application/json: schema: - description: Wraps the EmailLayout response under the `email_layout` key. + description: Wraps the MessageType response under the `message_type` key. example: - email_layout: + message_type: + archived_at: null created_at: "2021-01-01T00:00:00Z" + deleted_at: null + description: Email message type environment: development - footer_links: - - text: Example - url: http://example.com - html_layout: Hello, world! - key: transactional - name: Transactional + icon_name: email + key: email + name: Email + owner: user + preview:
      Hello, world!
      + semver: 1.0.0 sha: "1234567890" - text_layout: Hello, world! updated_at: "2021-01-01T00:00:00Z" + valid: true + variants: + - fields: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text + key: default + name: Default properties: - email_layout: - $ref: '#/components/schemas/EmailLayout' + message_type: + $ref: '#/components/schemas/MessageType' required: - - email_layout - title: WrappedEmailLayoutResponse + - message_type + title: WrappedMessageTypeResponse type: object x-struct: null x-validate: null description: OK - summary: Validate email layout + summary: Validate message type tags: - - Email layouts + - Message types x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -12435,16 +14888,16 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.emailLayouts.validate('email_layout_key', { + const response = await client.messageTypes.validate('email', { environment: 'development', - email_layout: { - html_layout: 'Hello, world!', - name: 'Transactional', - text_layout: 'Hello, world!', + message_type: { + description: 'This is a message type', + name: 'My Message Type', + preview: '
      Hello, world!
      ', }, }); - console.log(response.email_layout); + console.log(response.message_type); python: |- import os from knock_mapi import KnockMgmt @@ -12452,148 +14905,57 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.email_layouts.validate( - email_layout_key="email_layout_key", + response = client.message_types.validate( + message_type_key="email", environment="development", - email_layout={ - "html_layout": "Hello, world!", - "name": "Transactional", - "text_layout": "Hello, world!", + message_type={ + "description": "This is a message type", + "name": "My Message Type", + "preview": "
      Hello, world!
      ", }, ) - print(response.email_layout) + print(response.message_type) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.EmailLayouts.Validate( - context.TODO(), - "email_layout_key", - knockmapi.EmailLayoutValidateParams{ - Environment: "development", - EmailLayout: knockmapi.EmailLayoutValidateParamsEmailLayout{ - HTMLLayout: "Hello, world!", - Name: "Transactional", - TextLayout: "Hello, world!", - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.EmailLayout) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.MessageTypes.Validate( + context.TODO(), + "email", + knockmapi.MessageTypeValidateParams{ + Environment: "development", + MessageType: knockmapi.MessageTypeValidateParamsMessageType{ + Description: knockmapi.String("This is a message type"), + Name: "My Message Type", + Preview: "
      Hello, world!
      ", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.MessageType) } - /v1/commits/{id}/promote: - put: + /v1/translations/{locale_code}: + get: callbacks: {} - description: Promotes one change to the subsequent environment. - operationId: promoteOneCommit + description: Retrieve a translation by its locale and namespace, in a given environment. + operationId: getTranslation parameters: - - description: The target commit ID to promote to the subsequent environment. + - description: A specific locale code to filter translations for. in: path - name: id - required: true - schema: - type: string - x-struct: null - x-validate: null - responses: - "200": - content: - application/json: - schema: - description: Wraps the Commit response under the `commit` key. - example: - commit: - author: - email: john.doe@example.com - name: John Doe - commit_message: This is a commit message - created_at: "2021-01-01T00:00:00Z" - environment: development - id: 123e4567-e89b-12d3-a456-426614174000 - resource: - identifier: my-email-layout - type: email_layout - properties: - commit: - $ref: '#/components/schemas/Commit' - required: - - commit - title: WrappedCommitResponse - type: object - x-struct: null - x-validate: null - description: OK - summary: Promote one commit - tags: - - Commits - x-stainless-snippets: - typescript: |- - import KnockMgmt from '@knocklabs/mgmt'; - - const client = new KnockMgmt({ - serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted - }); - - const response = await client.commits.promoteOne('id'); - - console.log(response.commit); - python: |- - import os - from knock_mapi import KnockMgmt - - client = KnockMgmt( - service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted - ) - response = client.commits.promote_one( - "id", - ) - print(response.commit) - go: | - package main - - import ( - "context" - "fmt" - - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" - ) - - func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Commits.PromoteOne(context.TODO(), "id") - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Commit) - } - /v1/guides/{guide_key}/validate: - put: - callbacks: {} - description: | - Validates a guide payload without persisting it. - - Note: Validating a guide is only done in the development environment context. - operationId: validateGuide - parameters: - - description: The key of the guide. - in: path - name: guide_key + name: locale_code required: true schema: type: string @@ -12617,95 +14979,76 @@ paths: type: string x-struct: null x-validate: null - requestBody: - content: - application/json: - schema: - description: Wraps the GuideRequest request under the guide key. - example: - guide: - activation_url_patterns: - - directive: allow - pathname: /dashboard/* - channel_key: in-app-guide - description: A guide to help users get started with the application - name: Getting Started Guide - steps: - - name: Welcome to the App - ref: welcome-step - schema_key: tooltip - schema_semver: 1.0.0 - schema_variant_key: default - values: - text_field: value - target_audience_id: null - target_property_conditions: - all: - - argument: some_property - operator: equal_to - variable: recipient.property - properties: - guide: - $ref: '#/components/schemas/GuideRequest' - required: - - guide - title: WrappedGuideRequestRequest - type: object - x-struct: null - x-validate: null - description: Params - required: false + - description: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. + in: query + name: format + required: false + schema: + enum: + - json + - po + type: string + x-struct: null + x-validate: null + - description: A specific namespace to filter translations for. + in: query + name: namespace + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: A specific tenant to scope the translation to. + in: query + name: tenant + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: Whether to annotate the resource. Only used in the Knock CLI. + in: query + name: annotate + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. + in: query + name: hide_uncommitted_changes + required: false + schema: + type: boolean + x-struct: null + x-validate: null responses: "200": content: application/json: schema: - description: Wraps the Guide response under the `guide` key. + description: Wraps the Translation response under the `translation` key. example: - guide: - activation_url_patterns: - - directive: allow - pathname: /dashboard/* - active: true - archived_at: null - channel_key: in-app-guide - created_at: "2024-01-01T00:00:00Z" - description: A guide to help users get started with the application - environment: development - key: getting-started - name: Getting Started Guide - semver: 0.0.1 - sha: "1234567890" - steps: - - name: Welcome to the App - ref: welcome-step - schema_key: tooltip - schema_semver: 1.0.0 - schema_variant_key: default - values: - text_field: value - target_audience_id: null - target_property_conditions: - all: - - argument: some_property - operator: equal_to - variable: recipient.property - type: banner - updated_at: "2024-01-01T00:00:00Z" - valid: true + translation: + content: '{"hello":"Hello, world!"}' + format: json + inserted_at: "2021-01-01T00:00:00Z" + locale_code: en + namespace: my_app + updated_at: "2021-01-01T00:00:00Z" properties: - guide: - $ref: '#/components/schemas/Guide' + translation: + $ref: '#/components/schemas/Translation' required: - - guide - title: WrappedGuideResponse + - translation + title: WrappedTranslationResponse type: object x-struct: null x-validate: null description: OK - summary: Validate a guide + summary: Get translation tags: - - Guides + - Translations x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -12714,23 +15057,11 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.guides.validate('guide_key', { + const translation = await client.translations.retrieve('locale_code', { environment: 'development', - guide: { - channel_key: 'in-app-guide', - name: 'Getting Started Guide', - steps: [ - { - ref: 'welcome-step', - schema_key: 'tooltip', - schema_semver: '1.0.0', - schema_variant_key: 'default', - }, - ], - }, }); - console.log(response.guide); + console.log(translation.translation); python: |- import os from knock_mapi import KnockMgmt @@ -12738,70 +15069,51 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.guides.validate( - guide_key="guide_key", + translation = client.translations.retrieve( + locale_code="locale_code", environment="development", - guide={ - "channel_key": "in-app-guide", - "name": "Getting Started Guide", - "steps": [{ - "ref": "welcome-step", - "schema_key": "tooltip", - "schema_semver": "1.0.0", - "schema_variant_key": "default", - }], - }, ) - print(response.guide) + print(translation.translation) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Guides.Validate( - context.TODO(), - "guide_key", - knockmapi.GuideValidateParams{ - Environment: "development", - Guide: knockmapi.GuideValidateParamsGuide{ - ChannelKey: "in-app-guide", - Name: "Getting Started Guide", - Steps: []knockmapi.GuideStepParam{knockmapi.GuideStepParam{ - Ref: "welcome-step", - SchemaKey: "tooltip", - SchemaSemver: "1.0.0", - SchemaVariantKey: "default", - }}, - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Guide) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + translation, err := client.Translations.Get( + context.TODO(), + "locale_code", + knockmapi.TranslationGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", translation.Translation) } - /v1/message_types/{message_type_key}: - get: + put: callbacks: {} - description: Retrieve a message type by its key, in a given environment. - operationId: getMessageType + description: | + Updates a translation of a given locale code + namespace, or creates a new one if it does not yet exist. + + Note: this endpoint only operates on translations in the "development" environment. + operationId: upsertTranslation parameters: - - description: The key of the message type to retrieve. + - description: A locale code of the translation. in: path - name: message_type_key + name: locale_code required: true schema: - example: email type: string x-struct: null x-validate: null @@ -12823,120 +15135,44 @@ paths: type: string x-struct: null x-validate: null - - description: Whether to annotate the resource. Only used in the Knock CLI. + - description: An optional namespace that identifies the translation. in: query - name: annotate - required: false + name: namespace + required: true schema: - type: boolean + type: string x-struct: null x-validate: null - - description: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. + - description: An optional tenant to scope the translation to. in: query - name: hide_uncommitted_changes + name: tenant required: false schema: - type: boolean + type: string x-struct: null x-validate: null - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/MessageType' - description: OK - summary: Get message type - tags: - - Message types - x-stainless-snippets: - typescript: |- - import KnockMgmt from '@knocklabs/mgmt'; - - const client = new KnockMgmt({ - serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted - }); - - const messageType = await client.messageTypes.retrieve('email', { environment: 'development' }); - - console.log(messageType.valid); - python: |- - import os - from knock_mapi import KnockMgmt - - client = KnockMgmt( - service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted - ) - message_type = client.message_types.retrieve( - message_type_key="email", - environment="development", - ) - print(message_type.valid) - go: | - package main - - import ( - "context" - "fmt" - - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" - ) - - func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - messageType, err := client.MessageTypes.Get( - context.TODO(), - "email", - knockmapi.MessageTypeGetParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", messageType.Valid) - } - put: - callbacks: {} - description: | - Updates a message type, or creates a new one if it does not yet exist. - - Note: this endpoint only operates in the `development` environment. - operationId: upsertMessageType - parameters: - - description: The key of the message type to upsert. - in: path - name: message_type_key - required: true - schema: - example: email - type: string - x-struct: null - x-validate: null - - description: The environment slug. + - description: Optionally specify the returned content format. Supports 'json' and 'po'. Defaults to 'json'. in: query - name: environment - required: true + name: format + required: false schema: - example: development + enum: + - json + - po type: string x-struct: null x-validate: null - - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + - description: Whether to annotate the resource. Only used in the Knock CLI. in: query - name: branch + name: annotate required: false schema: - example: feature-branch - type: string + type: boolean x-struct: null x-validate: null - - description: Whether to annotate the resource. Only used in the Knock CLI. + - description: When set to true, forces the upsert to override existing content regardless of environment restrictions. This bypasses the development-only environment check and origin environment checks. in: query - name: annotate + name: force required: false schema: type: boolean @@ -12962,30 +15198,17 @@ paths: content: application/json: schema: - description: Wraps the MessageTypeRequest request under the message_type key. + description: Wraps the TranslationRequest request under the translation key. example: - message_type: - description: This is a message type - name: My Message Type - preview:
      Hello, world!
      - variants: - - fields: - - key: text_field - label: My text field - settings: - description: A description of the text field - max_length: 100 - min_length: 10 - required: true - type: text - key: default - name: Default + translation: + content: '{"hello":"Hello, world!"}' + format: json properties: - message_type: - $ref: '#/components/schemas/MessageTypeRequest' + translation: + $ref: '#/components/schemas/TranslationRequest' required: - - message_type - title: WrappedMessageTypeRequestRequest + - translation + title: WrappedTranslationRequestRequest type: object x-struct: null x-validate: null @@ -12996,48 +15219,28 @@ paths: content: application/json: schema: - description: Wraps the MessageType response under the `message_type` key. + description: Wraps the Translation response under the `translation` key. example: - message_type: - archived_at: null - created_at: "2021-01-01T00:00:00Z" - deleted_at: null - description: Email message type - environment: development - icon_name: email - key: email - name: Email - owner: user - preview:
      Hello, world!
      - semver: 1.0.0 - sha: "1234567890" + translation: + content: '{"hello":"Hello, world!"}' + format: json + inserted_at: "2021-01-01T00:00:00Z" + locale_code: en + namespace: my_app updated_at: "2021-01-01T00:00:00Z" - valid: true - variants: - - fields: - - key: text_field - label: My text field - settings: - description: A description of the text field - max_length: 100 - min_length: 10 - required: true - type: text - key: default - name: Default properties: - message_type: - $ref: '#/components/schemas/MessageType' + translation: + $ref: '#/components/schemas/Translation' required: - - message_type - title: WrappedMessageTypeResponse + - translation + title: WrappedTranslationResponse type: object x-struct: null x-validate: null description: OK - summary: Upsert message type + summary: Upsert translation tags: - - Message types + - Translations x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -13046,16 +15249,13 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.messageTypes.upsert('email', { + const response = await client.translations.upsert('locale_code', { environment: 'development', - message_type: { - description: 'This is a message type', - name: 'My Message Type', - preview: '
      Hello, world!
      ', - }, + namespace: 'namespace', + translation: { content: '{"hello":"Hello, world!"}', format: 'json' }, }); - console.log(response.message_type); + console.log(response.translation); python: |- import os from knock_mapi import KnockMgmt @@ -13063,94 +15263,76 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.message_types.upsert( - message_type_key="email", + response = client.translations.upsert( + locale_code="locale_code", environment="development", - message_type={ - "description": "This is a message type", - "name": "My Message Type", - "preview": "
      Hello, world!
      ", + namespace="namespace", + translation={ + "content": "{\"hello\":\"Hello, world!\"}", + "format": "json", }, ) - print(response.message_type) + print(response.translation) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.MessageTypes.Upsert( - context.TODO(), - "email", - knockmapi.MessageTypeUpsertParams{ - Environment: "development", - MessageType: knockmapi.MessageTypeUpsertParamsMessageType{ - Description: knockmapi.String("This is a message type"), - Name: "My Message Type", - Preview: "
      Hello, world!
      ", - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.MessageType) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Translations.Upsert( + context.TODO(), + "locale_code", + knockmapi.TranslationUpsertParams{ + Environment: "development", + Namespace: "namespace", + Translation: knockmapi.TranslationUpsertParamsTranslation{ + Content: `{"hello":"Hello, world!"}`, + Format: "json", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Translation) } - /v1/workflows/{workflow_key}: + /v1/environments: get: callbacks: {} - description: Retrieve a workflow by its key in a given environment. - operationId: getWorkflow + description: Returns a paginated list of environments. The environments will be returned in order of their index, with the `development` environment first. + operationId: listEnvironments parameters: - - description: The key of the workflow to retrieve. - in: path - name: workflow_key - required: true - schema: - type: string - x-struct: null - x-validate: null - - description: The environment slug. - in: query - name: environment - required: true - schema: - example: development - type: string - x-struct: null - x-validate: null - - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + - description: The cursor to fetch entries after. in: query - name: branch + name: after required: false schema: - example: feature-branch type: string x-struct: null x-validate: null - - description: Whether to annotate the resource. Only used in the Knock CLI. + - description: The cursor to fetch entries before. in: query - name: annotate + name: before required: false schema: - type: boolean + type: string x-struct: null x-validate: null - - description: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. + - description: The number of entries to fetch per-page. in: query - name: hide_uncommitted_changes + name: limit required: false schema: - type: boolean + type: integer x-struct: null x-validate: null responses: @@ -13158,11 +15340,45 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ShowWorkflowResponse' + description: A paginated list of Environment. Contains a list of entries and page information. + example: + entries: + - created_at: "2022-10-31T19:59:03Z" + deleted_at: null + hide_pii_data: false + label_color: '#000000' + last_commit_at: "2022-10-31T19:59:03Z" + name: Development + order: 0 + owner: system + slug: development + updated_at: "2022-10-31T19:59:03Z" + page_info: + after: null + before: null + page_size: 25 + properties: + entries: + description: A list of entries. + items: + $ref: '#/components/schemas/Environment' + nullable: false + type: array + x-struct: null + x-validate: null + page_info: + $ref: '#/components/schemas/PageInfo' + required: + - entries + - page_info + title: PaginatedEnvironmentResponse + type: object + x-struct: null + x-validate: null description: OK - summary: Get a workflow + summary: List environments tags: - - Workflows + - Environments x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -13171,9 +15387,10 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const workflow = await client.workflows.retrieve('workflow_key', { environment: 'development' }); - - console.log(workflow.valid); + // Automatically fetches more pages as needed. + for await (const environment of client.environments.list()) { + console.log(environment.hide_pii_data); + } python: |- import os from knock_mapi import KnockMgmt @@ -13181,49 +15398,42 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - workflow = client.workflows.retrieve( - workflow_key="workflow_key", - environment="development", - ) - print(workflow.valid) + page = client.environments.list() + page = page.entries[0] + print(page.hide_pii_data) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - workflow, err := client.Workflows.Get( - context.TODO(), - "workflow_key", - knockmapi.WorkflowGetParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", workflow.Valid) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Environments.List(context.TODO(), knockmapi.EnvironmentListParams{}) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } + /v1/email_layouts/{email_layout_key}/validate: put: callbacks: {} description: | - Updates a workflow of a given key, or creates a new one if it does not yet exist. + Validates an email layout payload without persisting it. - Note: this endpoint only operates on workflows in the `development` environment. - operationId: upsertWorkflow + Note: this endpoint only operates in the "development" environment. + operationId: validateEmailLayout parameters: - - description: The key of the workflow. + - description: The key of the email layout to validate. in: path - name: workflow_key + name: email_layout_key required: true schema: type: string @@ -13247,130 +15457,74 @@ paths: type: string x-struct: null x-validate: null - - description: Whether to annotate the resource. Only used in the Knock CLI. - in: query - name: annotate - required: false - schema: - type: boolean - x-struct: null - x-validate: null - - description: Whether to commit the resource at the same time as modifying it. - in: query - name: commit - required: false - schema: - type: boolean - x-struct: null - x-validate: null - - description: The message to commit the resource with, only used if `commit` is `true`. - in: query - name: commit_message - required: false - schema: - type: string - x-struct: null - x-validate: null requestBody: content: application/json: schema: - description: Wraps the WorkflowRequest request under the workflow key. + description: Wraps the EmailLayoutRequest request under the email_layout key. example: - workflow: - name: My Workflow - steps: - - channel_key: in-app-feed - name: Channel 1 - ref: channel_1 - template: - action_url: '{{ vars.app_url }}' - markdown_body: Hello **{{ recipient.name }}** - type: channel + email_layout: + branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' + footer_links: + - text: Example + url: http://example.com + html_layout: Hello, world! + name: Transactional + text_layout: Hello, world! properties: - workflow: - $ref: '#/components/schemas/WorkflowRequest' + email_layout: + $ref: '#/components/schemas/EmailLayoutRequest' required: - - workflow - title: WrappedWorkflowRequestRequest + - email_layout + title: WrappedEmailLayoutRequestRequest type: object x-struct: null x-validate: null - description: Params + description: Email layout required: false responses: "200": content: application/json: schema: - description: Wraps the Workflow response under the `workflow` key. + description: Wraps the EmailLayout response under the `email_layout` key. example: - workflow: - active: false - categories: - - marketing - - black-friday - conditions: - all: - - argument: admin - operator: equal_to - variable: recipient.role - created_at: "2022-12-16T19:07:50.027113Z" - description: This is a dummy workflow for demo purposes. + email_layout: + branding_overrides: + dark_logo_url: https://cdn.example.com/logo-dark.png + dark_primary_color: '#1A1A2E' + dark_primary_color_contrast: '#FFFFFF' + primary_color: '#4F46E5' + primary_color_contrast: '#FFFFFF' + created_at: "2021-01-01T00:00:00Z" environment: development - key: december-16-demo - name: december-16-demo - settings: - override_preferences: true - sha: f7e9d3b2a1c8e6m4k5j7h9g0i2l3n4p6q8r0t1u3v5w7x9y - steps: - - channel_key: in-app-feed - channel_type: in_app_feed - description: Main in-app feed - name: In-app step - ref: in_app_feed_1 - template: - action_url: '{{ data.onboarding_url }}' - markdown_body: Hello **{{ recipient.name }}**. Click here to get started. - type: channel - - ref: delay_1 - settings: - delay_for: - unit: hours - value: 1 - type: delay - - channel_key: postmark - channel_type: email - ref: email_1 - template: - html_body:

      Hello, {{ recipient.name }}! Welcome to {{ vars.app_name }} Get started here.

      - settings: - layout_key: default - subject: Welcome to {{ vars.app_name }}! - type: channel - trigger_data_json_schema: - properties: - onboarding_url: - type: string - required: - - onboarding_url - type: object - trigger_frequency: every_trigger - updated_at: "2023-02-08T22:15:19.846681Z" - valid: true + footer_links: + - text: Example + url: http://example.com + html_layout: Hello, world! + key: transactional + name: Transactional + sha: "1234567890" + text_layout: Hello, world! + updated_at: "2021-01-01T00:00:00Z" properties: - workflow: - $ref: '#/components/schemas/Workflow' + email_layout: + $ref: '#/components/schemas/EmailLayout' required: - - workflow - title: WrappedWorkflowResponse + - email_layout + title: WrappedEmailLayoutResponse type: object x-struct: null x-validate: null description: OK - summary: Upsert a workflow + summary: Validate email layout tags: - - Workflows + - Email layouts x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -13379,21 +15533,16 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.workflows.upsert('workflow_key', { + const response = await client.emailLayouts.validate('email_layout_key', { environment: 'development', - workflow: { - name: 'My Workflow', - steps: [ - { - ref: 'channel_1', - template: { markdown_body: 'Hello **{{ recipient.name }}**' }, - type: 'channel', - }, - ], + email_layout: { + html_layout: 'Hello, world!', + name: 'Transactional', + text_layout: 'Hello, world!', }, }); - console.log(response.workflow); + console.log(response.email_layout); python: |- import os from knock_mapi import KnockMgmt @@ -13401,90 +15550,93 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.workflows.upsert( - workflow_key="workflow_key", + response = client.email_layouts.validate( + email_layout_key="email_layout_key", environment="development", - workflow={ - "name": "My Workflow", - "steps": [{ - "ref": "channel_1", - "template": { - "markdown_body": "Hello **{{ recipient.name }}**" - }, - "type": "channel", - }], + email_layout={ + "html_layout": "Hello, world!", + "name": "Transactional", + "text_layout": "Hello, world!", }, ) - print(response.workflow) + print(response.email_layout) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Workflows.Upsert( - context.TODO(), - "workflow_key", - knockmapi.WorkflowUpsertParams{ - Environment: "development", - Workflow: knockmapi.WorkflowUpsertParamsWorkflow{ - Name: "My Workflow", - Steps: []knockmapi.WorkflowStepUnionParam{knockmapi.WorkflowStepUnionParam{ - OfWorkflowInAppFeedStep: &knockmapi.WorkflowInAppFeedStepParam{ - Ref: "channel_1", - Template: knockmapi.InAppFeedTemplateParam{ - MarkdownBody: "Hello **{{ recipient.name }}**", - }, - Type: knockmapi.WorkflowInAppFeedStepTypeChannel, - }, - }}, - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Workflow) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.EmailLayouts.Validate( + context.TODO(), + "email_layout_key", + knockmapi.EmailLayoutValidateParams{ + Environment: "development", + EmailLayout: knockmapi.EmailLayoutValidateParamsEmailLayout{ + HTMLLayout: "Hello, world!", + Name: "Transactional", + TextLayout: "Hello, world!", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.EmailLayout) } - /v1/branches/{branch_slug}: - delete: + /v1/commits/{id}/promote: + put: callbacks: {} - description: Deletes a branch by the `branch_slug`. - operationId: deleteBranch + description: Promotes one change to the subsequent environment. + operationId: promoteOneCommit parameters: - - description: The environment slug. - in: query - name: environment - required: true - schema: - example: development - type: string - x-struct: null - x-validate: null - - description: The slug of the branch to delete. + - description: The target commit ID to promote to the subsequent environment. in: path - name: branch_slug + name: id required: true schema: - example: feature-branch type: string x-struct: null x-validate: null responses: - "204": - description: No Content - summary: Delete a branch + "200": + content: + application/json: + schema: + description: Wraps the Commit response under the `commit` key. + example: + commit: + author: + email: john.doe@example.com + name: John Doe + commit_message: This is a commit message + created_at: "2021-01-01T00:00:00Z" + environment: development + id: 123e4567-e89b-12d3-a456-426614174000 + resource: + identifier: my-email-layout + type: email_layout + properties: + commit: + $ref: '#/components/schemas/Commit' + required: + - commit + title: WrappedCommitResponse + type: object + x-struct: null + x-validate: null + description: OK + summary: Promote one commit tags: - - Branches + - Commits x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -13493,7 +15645,9 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - await client.branches.delete('feature-branch', { environment: 'development' }); + const response = await client.commits.promoteOne('id'); + + console.log(response.commit); python: |- import os from knock_mapi import KnockMgmt @@ -13501,40 +15655,48 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - client.branches.delete( - branch_slug="feature-branch", - environment="development", - ) + response = client.commits.promote_one( + "id", + ) + print(response.commit) go: | package main import ( - "context" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - err := client.Branches.Delete( - context.TODO(), - "feature-branch", - knockmapi.BranchDeleteParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Commits.PromoteOne(context.TODO(), "id") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Commit) } - get: + /v1/guides/{guide_key}/validate: + put: callbacks: {} - description: Returns a single branch by the `branch_slug`. - operationId: getBranch + description: | + Validates a guide payload without persisting it. + + Note: Validating a guide is only done in the development environment context. + operationId: validateGuide parameters: + - description: The key of the guide. + in: path + name: guide_key + required: true + schema: + type: string + x-struct: null + x-validate: null - description: The environment slug. in: query name: environment @@ -13544,25 +15706,104 @@ paths: type: string x-struct: null x-validate: null - - description: The slug of the branch to retrieve. - in: path - name: branch_slug - required: true + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + in: query + name: branch + required: false schema: example: feature-branch type: string x-struct: null x-validate: null + requestBody: + content: + application/json: + schema: + description: Wraps the GuideRequest request under the guide key. + example: + guide: + activation_url_patterns: + - directive: allow + pathname: /dashboard/* + channel_key: in-app-guide + description: A guide to help users get started with the application + name: Getting Started Guide + steps: + - name: Welcome to the App + ref: welcome-step + schema_key: tooltip + schema_semver: 1.0.0 + schema_variant_key: default + values: + text_field: value + target_audience_id: null + target_property_conditions: + all: + - argument: some_property + operator: equal_to + variable: recipient.property + properties: + guide: + $ref: '#/components/schemas/GuideRequest' + required: + - guide + title: WrappedGuideRequestRequest + type: object + x-struct: null + x-validate: null + description: Params + required: false responses: "200": content: application/json: schema: - $ref: '#/components/schemas/Branch' + description: Wraps the Guide response under the `guide` key. + example: + guide: + activation_url_patterns: + - directive: allow + pathname: /dashboard/* + active: true + archived_at: null + channel_key: in-app-guide + created_at: "2024-01-01T00:00:00Z" + description: A guide to help users get started with the application + environment: development + key: getting-started + name: Getting Started Guide + semver: 0.0.1 + sha: "1234567890" + steps: + - name: Welcome to the App + ref: welcome-step + schema_key: tooltip + schema_semver: 1.0.0 + schema_variant_key: default + values: + text_field: value + target_audience_id: null + target_property_conditions: + all: + - argument: some_property + operator: equal_to + variable: recipient.property + type: banner + updated_at: "2024-01-01T00:00:00Z" + valid: true + properties: + guide: + $ref: '#/components/schemas/Guide' + required: + - guide + title: WrappedGuideResponse + type: object + x-struct: null + x-validate: null description: OK - summary: Get a branch + summary: Validate a guide tags: - - Branches + - Guides x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -13571,9 +15812,23 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const branch = await client.branches.retrieve('feature-branch', { environment: 'development' }); + const response = await client.guides.validate('guide_key', { + environment: 'development', + guide: { + channel_key: 'in-app-guide', + name: 'Getting Started Guide', + steps: [ + { + ref: 'welcome-step', + schema_key: 'tooltip', + schema_semver: '1.0.0', + schema_variant_key: 'default', + }, + ], + }, + }); - console.log(branch.created_at); + console.log(response.guide); python: |- import os from knock_mapi import KnockMgmt @@ -13581,71 +15836,141 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - branch = client.branches.retrieve( - branch_slug="feature-branch", + response = client.guides.validate( + guide_key="guide_key", environment="development", + guide={ + "channel_key": "in-app-guide", + "name": "Getting Started Guide", + "steps": [{ + "ref": "welcome-step", + "schema_key": "tooltip", + "schema_semver": "1.0.0", + "schema_variant_key": "default", + }], + }, ) - print(branch.created_at) + print(response.guide) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - branch, err := client.Branches.Get( - context.TODO(), - "feature-branch", - knockmapi.BranchGetParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", branch.CreatedAt) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Guides.Validate( + context.TODO(), + "guide_key", + knockmapi.GuideValidateParams{ + Environment: "development", + Guide: knockmapi.GuideValidateParamsGuide{ + ChannelKey: "in-app-guide", + Name: "Getting Started Guide", + Steps: []knockmapi.GuideStepParam{{ + Ref: "welcome-step", + SchemaKey: "tooltip", + SchemaSemver: "1.0.0", + SchemaVariantKey: "default", + }}, + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Guide) } - post: + /v1/channel_groups/{channel_group_key}: + delete: callbacks: {} - description: Creates a new branch off of the development environment with the given slug. - operationId: createBranch + description: Archives (soft deletes) a channel group by key. + operationId: deleteChannelGroup parameters: - - description: The environment slug. - in: query - name: environment + - description: The key of the channel group to delete. + in: path + name: channel_group_key required: true schema: - example: development type: string x-struct: null x-validate: null - - description: The slug for the new branch to create. + responses: + "204": + content: + application/json: {} + description: No Content + summary: Delete a channel group + tags: + - Channel Groups + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + await client.channelGroups.delete('channel_group_key'); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + client.channel_groups.delete( + "channel_group_key", + ) + go: | + package main + + import ( + "context" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + err := client.ChannelGroups.Delete(context.TODO(), "channel_group_key") + if err != nil { + panic(err.Error()) + } + } + get: + callbacks: {} + description: Get a channel group by its key. + operationId: getChannelGroup + parameters: + - description: The key of the channel group to retrieve. in: path - name: branch_slug + name: channel_group_key required: true schema: - example: feature-branch type: string x-struct: null x-validate: null responses: - "201": + "200": content: application/json: schema: - $ref: '#/components/schemas/Branch' - description: Created - summary: Create a branch + $ref: '#/components/schemas/ChannelGroup' + description: OK + summary: Get a channel group tags: - - Branches + - Channel Groups x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -13654,9 +15979,9 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const branch = await client.branches.create('feature-branch', { environment: 'development' }); + const channelGroup = await client.channelGroups.retrieve('channel_group_key'); - console.log(branch.created_at); + console.log(channelGroup.channel_rules); python: |- import os from knock_mapi import KnockMgmt @@ -13664,66 +15989,1452 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - branch = client.branches.create( - branch_slug="feature-branch", - environment="development", + channel_group = client.channel_groups.retrieve( + "channel_group_key", ) - print(branch.created_at) + print(channel_group.channel_rules) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - branch, err := client.Branches.New( - context.TODO(), - "feature-branch", - knockmapi.BranchNewParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", branch.CreatedAt) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + channelGroup, err := client.ChannelGroups.Get(context.TODO(), "channel_group_key") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", channelGroup.ChannelRules) } - /v1/commits/promote: put: callbacks: {} - description: Promote all changes across all resources to the target environment from its preceding environment. - operationId: promoteAllCommits + description: Creates or updates a channel group by key. + operationId: upsertChannelGroup parameters: - - description: | - A slug of the target environment to which you want to promote all changes from its directly preceding environment. + - description: The key of the channel group to upsert. + in: path + name: channel_group_key + required: true + schema: + type: string + x-struct: null + x-validate: null + requestBody: + content: + application/json: + schema: + description: Wraps the ChannelGroupRequest request under the channel_group key. + example: + channel_group: + channel_rules: + - channel_key: push-fcm + index: 0 + rule_type: always + channel_type: push + name: Push Notification Group + operator: any + properties: + channel_group: + $ref: '#/components/schemas/ChannelGroupRequest' + required: + - channel_group + title: WrappedChannelGroupRequestRequest + type: object + x-struct: null + x-validate: null + description: Params + required: false + responses: + "200": + content: + application/json: + schema: + description: Wraps the ChannelGroup response under the `channel_group` key. + example: + channel_group: + channel_rules: + - channel: + archived_at: null + created_at: "2021-01-01T00:00:00Z" + custom_icon_url: null + id: 01234567-89ab-cdef-0123-456789abcdef + key: my-sendgrid-channel + name: My Sendgrid Channel + provider: sendgrid + type: email + updated_at: "2021-01-01T00:00:00Z" + created_at: "2021-01-01T00:00:00Z" + index: 0 + rule_type: always + updated_at: "2021-01-01T00:00:00Z" + channel_type: push + created_at: "2021-01-01T00:00:00Z" + key: push-group + name: Push Notification Group + operator: any + source: user + updated_at: "2021-01-01T00:00:00Z" + properties: + channel_group: + $ref: '#/components/schemas/ChannelGroup' + required: + - channel_group + title: WrappedChannelGroupResponse + type: object + x-struct: null + x-validate: null + description: OK + summary: Upsert a channel group + tags: + - Channel Groups + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const response = await client.channelGroups.upsert('channel_group_key', { + channel_group: { channel_type: 'push', name: 'Push Notification Group' }, + }); + + console.log(response.channel_group); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + response = client.channel_groups.upsert( + channel_group_key="channel_group_key", + channel_group={ + "channel_type": "push", + "name": "Push Notification Group", + }, + ) + print(response.channel_group) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.ChannelGroups.Upsert( + context.TODO(), + "channel_group_key", + knockmapi.ChannelGroupUpsertParams{ + ChannelGroup: knockmapi.ChannelGroupUpsertParamsChannelGroup{ + ChannelType: "push", + Name: "Push Notification Group", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.ChannelGroup) + } + /v1/message_types/{message_type_key}: + get: + callbacks: {} + description: Retrieve a message type by its key, in a given environment. + operationId: getMessageType + parameters: + - description: The key of the message type to retrieve. + in: path + name: message_type_key + required: true + schema: + example: email + type: string + x-struct: null + x-validate: null + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + in: query + name: branch + required: false + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + - description: Whether to annotate the resource. Only used in the Knock CLI. + in: query + name: annotate + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. + in: query + name: hide_uncommitted_changes + required: false + schema: + type: boolean + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/MessageType' + description: OK + summary: Get message type + tags: + - Message types + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const messageType = await client.messageTypes.retrieve('email', { environment: 'development' }); + + console.log(messageType.valid); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + message_type = client.message_types.retrieve( + message_type_key="email", + environment="development", + ) + print(message_type.valid) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + messageType, err := client.MessageTypes.Get( + context.TODO(), + "email", + knockmapi.MessageTypeGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", messageType.Valid) + } + put: + callbacks: {} + description: | + Updates a message type, or creates a new one if it does not yet exist. + + Note: this endpoint only operates in the `development` environment. + operationId: upsertMessageType + parameters: + - description: The key of the message type to upsert. + in: path + name: message_type_key + required: true + schema: + example: email + type: string + x-struct: null + x-validate: null + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + in: query + name: branch + required: false + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + - description: Whether to annotate the resource. Only used in the Knock CLI. + in: query + name: annotate + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: When set to true, forces the upsert to override existing content regardless of environment restrictions. This bypasses the development-only environment check and origin environment checks. + in: query + name: force + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: Whether to commit the resource at the same time as modifying it. + in: query + name: commit + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: The message to commit the resource with, only used if `commit` is `true`. + in: query + name: commit_message + required: false + schema: + type: string + x-struct: null + x-validate: null + requestBody: + content: + application/json: + schema: + description: Wraps the MessageTypeRequest request under the message_type key. + example: + message_type: + description: This is a message type + name: My Message Type + preview:
      Hello, world!
      + variants: + - fields: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text + key: default + name: Default + properties: + message_type: + $ref: '#/components/schemas/MessageTypeRequest' + required: + - message_type + title: WrappedMessageTypeRequestRequest + type: object + x-struct: null + x-validate: null + description: Params + required: false + responses: + "200": + content: + application/json: + schema: + description: Wraps the MessageType response under the `message_type` key. + example: + message_type: + archived_at: null + created_at: "2021-01-01T00:00:00Z" + deleted_at: null + description: Email message type + environment: development + icon_name: email + key: email + name: Email + owner: user + preview:
      Hello, world!
      + semver: 1.0.0 + sha: "1234567890" + updated_at: "2021-01-01T00:00:00Z" + valid: true + variants: + - fields: + - key: text_field + label: My text field + settings: + description: A description of the text field + max_length: 100 + min_length: 10 + required: true + type: text + key: default + name: Default + properties: + message_type: + $ref: '#/components/schemas/MessageType' + required: + - message_type + title: WrappedMessageTypeResponse + type: object + x-struct: null + x-validate: null + description: OK + summary: Upsert message type + tags: + - Message types + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const response = await client.messageTypes.upsert('email', { + environment: 'development', + message_type: { + description: 'This is a message type', + name: 'My Message Type', + preview: '
      Hello, world!
      ', + }, + }); + + console.log(response.message_type); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + response = client.message_types.upsert( + message_type_key="email", + environment="development", + message_type={ + "description": "This is a message type", + "name": "My Message Type", + "preview": "
      Hello, world!
      ", + }, + ) + print(response.message_type) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.MessageTypes.Upsert( + context.TODO(), + "email", + knockmapi.MessageTypeUpsertParams{ + Environment: "development", + MessageType: knockmapi.MessageTypeUpsertParamsMessageType{ + Description: knockmapi.String("This is a message type"), + Name: "My Message Type", + Preview: "
      Hello, world!
      ", + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.MessageType) + } + /v1/workflows/{workflow_key}: + get: + callbacks: {} + description: Retrieve a workflow by its key in a given environment. + operationId: getWorkflow + parameters: + - description: The key of the workflow to retrieve. + in: path + name: workflow_key + required: true + schema: + type: string + x-struct: null + x-validate: null + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + in: query + name: branch + required: false + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + - description: Whether to annotate the resource. Only used in the Knock CLI. + in: query + name: annotate + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. + in: query + name: hide_uncommitted_changes + required: false + schema: + type: boolean + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ShowWorkflowResponse' + description: OK + summary: Get a workflow + tags: + - Workflows + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const workflow = await client.workflows.retrieve('workflow_key', { environment: 'development' }); + + console.log(workflow.valid); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + workflow = client.workflows.retrieve( + workflow_key="workflow_key", + environment="development", + ) + print(workflow.valid) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + workflow, err := client.Workflows.Get( + context.TODO(), + "workflow_key", + knockmapi.WorkflowGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", workflow.Valid) + } + put: + callbacks: {} + description: | + Updates a workflow of a given key, or creates a new one if it does not yet exist. + + Note: this endpoint only operates on workflows in the `development` environment. + operationId: upsertWorkflow + parameters: + - description: The key of the workflow. + in: path + name: workflow_key + required: true + schema: + type: string + x-struct: null + x-validate: null + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + in: query + name: branch + required: false + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + - description: Whether to annotate the resource. Only used in the Knock CLI. + in: query + name: annotate + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: When set to true, forces the upsert to override existing content regardless of environment restrictions. This bypasses the development-only environment check and origin environment checks. + in: query + name: force + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: Whether to commit the resource at the same time as modifying it. + in: query + name: commit + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: The message to commit the resource with, only used if `commit` is `true`. + in: query + name: commit_message + required: false + schema: + type: string + x-struct: null + x-validate: null + requestBody: + content: + application/json: + schema: + description: Wraps the WorkflowRequest request under the workflow key. + example: + workflow: + name: My Workflow + steps: + - channel_key: in-app-feed + name: Channel 1 + ref: channel_1 + template: + action_url: '{{ vars.app_url }}' + markdown_body: Hello **{{ recipient.name }}** + type: channel + properties: + workflow: + $ref: '#/components/schemas/WorkflowRequest' + required: + - workflow + title: WrappedWorkflowRequestRequest + type: object + x-struct: null + x-validate: null + description: Params + required: false + responses: + "200": + content: + application/json: + schema: + description: Wraps the Workflow response under the `workflow` key. + example: + workflow: + active: false + categories: + - marketing + - black-friday + conditions: + all: + - argument: admin + operator: equal_to + variable: recipient.role + created_at: "2022-12-16T19:07:50.027113Z" + description: This is a dummy workflow for demo purposes. + environment: development + key: december-16-demo + name: december-16-demo + settings: + override_preferences: true + sha: f7e9d3b2a1c8e6m4k5j7h9g0i2l3n4p6q8r0t1u3v5w7x9y + steps: + - channel_key: in-app-feed + channel_type: in_app_feed + description: Main in-app feed + name: In-app step + ref: in_app_feed_1 + template: + action_url: '{{ data.onboarding_url }}' + markdown_body: Hello **{{ recipient.name }}**. Click here to get started. + type: channel + - ref: delay_1 + settings: + delay_for: + unit: hours + value: 1 + type: delay + - channel_key: postmark + channel_type: email + ref: email_1 + template: + html_body:

      Hello, {{ recipient.name }}! Welcome to {{ vars.app_name }} Get started here.

      + settings: + layout_key: default + subject: Welcome to {{ vars.app_name }}! + type: channel + trigger_data_json_schema: + properties: + onboarding_url: + type: string + required: + - onboarding_url + type: object + trigger_frequency: every_trigger + updated_at: "2023-02-08T22:15:19.846681Z" + valid: true + properties: + workflow: + $ref: '#/components/schemas/Workflow' + required: + - workflow + title: WrappedWorkflowResponse + type: object + x-struct: null + x-validate: null + description: OK + summary: Upsert a workflow + tags: + - Workflows + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const response = await client.workflows.upsert('workflow_key', { + environment: 'development', + workflow: { + name: 'My Workflow', + steps: [ + { + ref: 'channel_1', + template: { markdown_body: 'Hello **{{ recipient.name }}**' }, + type: 'channel', + }, + ], + }, + }); + + console.log(response.workflow); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + response = client.workflows.upsert( + workflow_key="workflow_key", + environment="development", + workflow={ + "name": "My Workflow", + "steps": [{ + "ref": "channel_1", + "template": { + "markdown_body": "Hello **{{ recipient.name }}**" + }, + "type": "channel", + }], + }, + ) + print(response.workflow) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Workflows.Upsert( + context.TODO(), + "workflow_key", + knockmapi.WorkflowUpsertParams{ + Environment: "development", + Workflow: knockmapi.WorkflowUpsertParamsWorkflow{ + Name: "My Workflow", + Steps: []knockmapi.WorkflowStepUnionParam{{ + OfWorkflowInAppFeedStep: &knockmapi.WorkflowInAppFeedStepParam{ + Ref: "channel_1", + Template: knockmapi.InAppFeedTemplateParam{ + MarkdownBody: "Hello **{{ recipient.name }}**", + }, + Type: knockmapi.WorkflowInAppFeedStepTypeChannel, + }, + }}, + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Workflow) + } + /v1/branches/{branch_slug}: + delete: + callbacks: {} + description: Deletes a branch by the `branch_slug`. + operationId: deleteBranch + parameters: + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug of the branch to delete. + in: path + name: branch_slug + required: true + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + responses: + "204": + description: No Content + summary: Delete a branch + tags: + - Branches + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + await client.branches.delete('feature-branch', { environment: 'development' }); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + client.branches.delete( + branch_slug="feature-branch", + environment="development", + ) + go: | + package main + + import ( + "context" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + err := client.Branches.Delete( + context.TODO(), + "feature-branch", + knockmapi.BranchDeleteParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + } + get: + callbacks: {} + description: Returns a single branch by the `branch_slug`. + operationId: getBranch + parameters: + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug of the branch to retrieve. + in: path + name: branch_slug + required: true + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + description: OK + summary: Get a branch + tags: + - Branches + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const branch = await client.branches.retrieve('feature-branch', { environment: 'development' }); + + console.log(branch.created_at); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + branch = client.branches.retrieve( + branch_slug="feature-branch", + environment="development", + ) + print(branch.created_at) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + branch, err := client.Branches.Get( + context.TODO(), + "feature-branch", + knockmapi.BranchGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", branch.CreatedAt) + } + post: + callbacks: {} + description: Creates a new branch off of the development environment with the given slug. + operationId: createBranch + parameters: + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug for the new branch to create. + in: path + name: branch_slug + required: true + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + responses: + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/Branch' + description: Created + summary: Create a branch + tags: + - Branches + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const branch = await client.branches.create('feature-branch', { environment: 'development' }); + + console.log(branch.created_at); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + branch = client.branches.create( + branch_slug="feature-branch", + environment="development", + ) + print(branch.created_at) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + branch, err := client.Branches.New( + context.TODO(), + "feature-branch", + knockmapi.BranchNewParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", branch.CreatedAt) + } + /v1/commits/promote: + put: + callbacks: {} + description: Promote all changes across all resources to the target environment from its preceding environment. + operationId: promoteAllCommits + parameters: + - description: | + A slug of the target environment to which you want to promote all changes from its directly preceding environment. For example, if you have three environments β€œdevelopment”, β€œstaging”, and β€œproduction” (in that order), setting this param to β€œproduction” will promote all commits not currently in production from staging. - When this param is set to `"development"`, the `"branch"` param must be provided. + When this param is set to `"development"`, the `"branch"` param must be provided. + in: query + name: to_environment + required: true + schema: + type: string + x-struct: null + x-validate: null + - description: The slug of the branch to promote all changes from. + in: query + name: branch + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: Filter commits to promote by resource type(s). Accepts a single type or array of types. Can be combined with resource_id to filter for specific resources. + in: query + name: resource_type + required: false + schema: + oneOf: + - enum: + - audience + - email_layout + - guide + - message_type + - partial + - translation + - workflow + type: string + x-struct: null + x-validate: null + - items: + enum: + - audience + - email_layout + - guide + - message_type + - partial + - translation + - workflow + type: string + x-struct: null + x-validate: null + type: array + x-struct: null + x-validate: null + x-struct: null + x-validate: null + - description: Filter commits to promote by resource identifier. Must be used together with resource_type. + in: query + name: resource_id + required: false + schema: + type: string + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + description: The response from promoting all changes. + example: + result: success + properties: + result: + description: The result of the promote operation. + example: success + type: string + x-struct: null + x-validate: null + required: + - result + title: PromoteAllResponse + type: object + x-struct: null + x-validate: null + description: OK + summary: Promote all changes + tags: + - Commits + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const response = await client.commits.promoteAll({ to_environment: 'to_environment' }); + + console.log(response.result); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + response = client.commits.promote_all( + to_environment="to_environment", + ) + print(response.result) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Commits.PromoteAll(context.TODO(), knockmapi.CommitPromoteAllParams{ + ToEnvironment: "to_environment", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Result) + } + /v1/commits: + get: + callbacks: {} + description: Returns a paginated list of commits in a given environment. The commits are ordered from most recent first. + operationId: listCommits + parameters: + - description: The environment slug. + in: query + name: environment + required: true + schema: + example: development + type: string + x-struct: null + x-validate: null + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + in: query + name: branch + required: false + schema: + example: feature-branch + type: string + x-struct: null + x-validate: null + - description: Whether to show commits in the given environment that have not been promoted to the subsequent environment (false) or commits which have been promoted (true). + in: query + name: promoted + required: false + schema: + type: boolean + x-struct: null + x-validate: null + - description: Filter commits by resource type(s). Accepts a single type or array of types. Can be combined with resource_id to filter for specific resources. + in: query + name: resource_type + required: false + schema: + oneOf: + - enum: + - audience + - email_layout + - guide + - message_type + - partial + - translation + - workflow + type: string + x-struct: null + x-validate: null + - items: + enum: + - audience + - email_layout + - guide + - message_type + - partial + - translation + - workflow + type: string + x-struct: null + x-validate: null + type: array + x-struct: null + x-validate: null + x-struct: null + x-validate: null + - description: Filter commits by resource identifier. Must be used together with resource_type. For most resources, this will be the resource key. In the case of translations, this will be the locale code and namespace, separated by a `/`. For example, `en/courses` or `en`. + in: query + name: resource_id + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: The cursor to fetch entries after. + in: query + name: after + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: The cursor to fetch entries before. + in: query + name: before + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: The number of entries to fetch per-page. + in: query + name: limit + required: false + schema: + type: integer + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + description: A paginated list of Commit. Contains a list of entries and page information. + example: + entries: + - author: + email: john.doe@example.com + name: John Doe + commit_message: This is a commit message + created_at: "2021-01-01T00:00:00Z" + environment: development + id: 123e4567-e89b-12d3-a456-426614174000 + resource: + identifier: my-email-layout + type: email_layout + page_info: + after: null + before: null + page_size: 25 + properties: + entries: + description: A list of entries. + items: + $ref: '#/components/schemas/Commit' + nullable: false + type: array + x-struct: null + x-validate: null + page_info: + $ref: '#/components/schemas/PageInfo' + required: + - entries + - page_info + title: PaginatedCommitResponse + type: object + x-struct: null + x-validate: null + description: OK + summary: List commits + tags: + - Commits + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + // Automatically fetches more pages as needed. + for await (const commit of client.commits.list({ environment: 'development' })) { + console.log(commit.id); + } + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + page = client.commits.list( + environment="development", + ) + page = page.entries[0] + print(page.id) + go: | + package main + + import ( + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Commits.List(context.TODO(), knockmapi.CommitListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) + } + put: + callbacks: {} + description: Commit all changes across all resources in the development environment. + operationId: commitAllChanges + parameters: + - description: The environment slug. in: query - name: to_environment + name: environment required: true schema: + example: development type: string x-struct: null x-validate: null - - description: The slug of the branch to promote all changes from. + - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. in: query name: branch required: false schema: + example: feature-branch type: string x-struct: null x-validate: null - - description: Filter commits to promote by resource type(s). Accepts a single type or array of types. Can be combined with resource_id to filter for specific resources. + - description: An optional message to include in a commit. + in: query + name: commit_message + required: false + schema: + type: string + x-struct: null + x-validate: null + - description: Filter changes to commit by resource type(s). Accepts a single type or array of types. Can be combined with resource_id to filter for specific resources. in: query name: resource_type required: false @@ -13757,7 +17468,7 @@ paths: x-validate: null x-struct: null x-validate: null - - description: Filter commits to promote by resource identifier. Must be used together with resource_type. + - description: Filter changes to commit by resource identifier. Must be used together with resource_type. in: query name: resource_id required: false @@ -13770,24 +17481,24 @@ paths: content: application/json: schema: - description: The response from promoting all changes. + description: The response from committing all changes. example: result: success properties: result: - description: The result of the promote operation. + description: The result of the commit operation. example: success type: string x-struct: null x-validate: null required: - result - title: PromoteAllResponse + title: CommitAllResponse type: object x-struct: null x-validate: null description: OK - summary: Promote all changes + summary: Commit all changes tags: - Commits x-stainless-snippets: @@ -13798,7 +17509,7 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.commits.promoteAll({ to_environment: 'to_environment' }); + const response = await client.commits.commitAll({ environment: 'development' }); console.log(response.result); python: |- @@ -13808,38 +17519,38 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.commits.promote_all( - to_environment="to_environment", + response = client.commits.commit_all( + environment="development", ) print(response.result) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Commits.PromoteAll(context.TODO(), knockmapi.CommitPromoteAllParams{ - ToEnvironment: "to_environment", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Result) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Commits.CommitAll(context.TODO(), knockmapi.CommitCommitAllParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Result) } - /v1/commits: + /v1/audiences: get: callbacks: {} - description: Returns a paginated list of commits in a given environment. The commits are ordered from most recent first. - operationId: listCommits + description: Returns a paginated list of audiences for the given environment. + operationId: listAudiences parameters: - description: The environment slug. in: query @@ -13859,78 +17570,44 @@ paths: type: string x-struct: null x-validate: null - - description: Whether to show commits in the given environment that have not been promoted to the subsequent environment (false) or commits which have been promoted (true). - in: query - name: promoted - required: false - schema: - type: boolean - x-struct: null - x-validate: null - - description: Filter commits by resource type(s). Accepts a single type or array of types. Can be combined with resource_id to filter for specific resources. + - description: The cursor to fetch entries after. in: query - name: resource_type + name: after required: false schema: - oneOf: - - enum: - - audience - - email_layout - - guide - - message_type - - partial - - translation - - workflow - type: string - x-struct: null - x-validate: null - - items: - enum: - - audience - - email_layout - - guide - - message_type - - partial - - translation - - workflow - type: string - x-struct: null - x-validate: null - type: array - x-struct: null - x-validate: null + type: string x-struct: null x-validate: null - - description: Filter commits by resource identifier. Must be used together with resource_type. For most resources, this will be the resource key. In the case of translations, this will be the locale code and namespace, separated by a `/`. For example, `en/courses` or `en`. + - description: The cursor to fetch entries before. in: query - name: resource_id + name: before required: false schema: type: string x-struct: null x-validate: null - - description: The cursor to fetch entries after. + - description: The number of entries to fetch per-page. in: query - name: after + name: limit required: false schema: - type: string + type: integer x-struct: null x-validate: null - - description: The cursor to fetch entries before. + - description: Whether to annotate the resource. Only used in the Knock CLI. in: query - name: before + name: annotate required: false schema: - type: string + type: boolean x-struct: null x-validate: null - - description: The number of entries to fetch per-page. + - description: Whether to hide uncommitted changes. When true, only committed changes will be returned. When false, both committed and uncommitted changes will be returned. in: query - name: limit + name: hide_uncommitted_changes required: false schema: - type: integer + type: boolean x-struct: null x-validate: null responses: @@ -13938,19 +17615,22 @@ paths: content: application/json: schema: - description: A paginated list of Commit. Contains a list of entries and page information. + description: A paginated list of Audience. Contains a list of entries and page information. example: entries: - - author: - email: john.doe@example.com - name: John Doe - commit_message: This is a commit message - created_at: "2021-01-01T00:00:00Z" + - created_at: "2024-01-15T10:30:00Z" + description: Customers who signed up in the last 30 days. environment: development - id: 123e4567-e89b-12d3-a456-426614174000 - resource: - identifier: my-email-layout - type: email_layout + key: new-customers + name: New customers + segments: + - conditions: + - argument: 30_days_ago + operator: greater_than + property: recipient.created_at + sha: a1b2c3d4e5f6 + type: dynamic + updated_at: "2024-06-20T14:45:00Z" page_info: after: null before: null @@ -13959,7 +17639,7 @@ paths: entries: description: A list of entries. items: - $ref: '#/components/schemas/Commit' + $ref: '#/components/schemas/Audience' nullable: false type: array x-struct: null @@ -13969,14 +17649,14 @@ paths: required: - entries - page_info - title: PaginatedCommitResponse + title: PaginatedAudienceResponse type: object x-struct: null x-validate: null description: OK - summary: List commits + summary: List audiences tags: - - Commits + - Audiences x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -13986,8 +17666,8 @@ paths: }); // Automatically fetches more pages as needed. - for await (const commit of client.commits.list({ environment: 'development' })) { - console.log(commit.id); + for await (const audience of client.audiences.list({ environment: 'development' })) { + console.log(audience); } python: |- import os @@ -13996,105 +17676,78 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - page = client.commits.list( + page = client.audiences.list( environment="development", ) page = page.entries[0] - print(page.id) + print(page) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.Commits.List(context.TODO(), knockmapi.CommitListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Audiences.List(context.TODO(), knockmapi.AudienceListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } - put: + /v1/members: + get: callbacks: {} - description: Commit all changes across all resources in the development environment. - operationId: commitAllChanges + description: Returns a paginated list of members for the current account. Optionally filter by role. + operationId: listMembers parameters: - - description: The environment slug. + - description: 'Filter members by role. One of: owner, admin, member, production_only_member, billing, support.' in: query - name: environment - required: true + name: role + required: false schema: - example: development type: string x-struct: null x-validate: null - - description: The slug of a branch to use. This option can only be used when `environment` is `"development"`. + - description: Filter members by email address (exact match). in: query - name: branch + name: email required: false schema: - example: feature-branch type: string x-struct: null x-validate: null - - description: An optional message to include in a commit. + - description: The cursor to fetch entries after. in: query - name: commit_message + name: after required: false schema: type: string x-struct: null x-validate: null - - description: Filter changes to commit by resource type(s). Accepts a single type or array of types. Can be combined with resource_id to filter for specific resources. + - description: The cursor to fetch entries before. in: query - name: resource_type + name: before required: false schema: - oneOf: - - enum: - - audience - - email_layout - - guide - - message_type - - partial - - translation - - workflow - type: string - x-struct: null - x-validate: null - - items: - enum: - - audience - - email_layout - - guide - - message_type - - partial - - translation - - workflow - type: string - x-struct: null - x-validate: null - type: array - x-struct: null - x-validate: null + type: string x-struct: null x-validate: null - - description: Filter changes to commit by resource identifier. Must be used together with resource_type. + - description: The number of entries to fetch per-page. in: query - name: resource_id + name: limit required: false schema: - type: string + type: integer x-struct: null x-validate: null responses: @@ -14102,26 +17755,46 @@ paths: content: application/json: schema: - description: The response from committing all changes. + description: A paginated list of Member. Contains a list of entries and page information. example: - result: success + entries: + - created_at: "2024-01-15T10:30:00Z" + id: d4b8e8e0-1234-5678-9abc-def012345678 + role: admin + updated_at: "2024-06-20T14:45:00Z" + user: + avatar_url: https://www.gravatar.com/avatar/abc123 + created_at: "2024-01-10T08:00:00Z" + email: jane@example.com + id: a1b2c3d4-5678-9abc-def0-123456789abc + name: Jane Doe + updated_at: "2024-06-18T12:00:00Z" + page_info: + after: null + before: null + page_size: 25 properties: - result: - description: The result of the commit operation. - example: success - type: string + entries: + description: A list of entries. + items: + $ref: '#/components/schemas/Member' + nullable: false + type: array x-struct: null x-validate: null + page_info: + $ref: '#/components/schemas/PageInfo' required: - - result - title: CommitAllResponse + - entries + - page_info + title: PaginatedMemberResponse type: object x-struct: null x-validate: null description: OK - summary: Commit all changes + summary: List members tags: - - Commits + - Members x-stainless-snippets: typescript: |- import KnockMgmt from '@knocklabs/mgmt'; @@ -14130,9 +17803,10 @@ paths: serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted }); - const response = await client.commits.commitAll({ environment: 'development' }); - - console.log(response.result); + // Automatically fetches more pages as needed. + for await (const member of client.members.list()) { + console.log(member.id); + } python: |- import os from knock_mapi import KnockMgmt @@ -14140,32 +17814,29 @@ paths: client = KnockMgmt( service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted ) - response = client.commits.commit_all( - environment="development", - ) - print(response.result) + page = client.members.list() + page = page.entries[0] + print(page.id) go: | package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Commits.CommitAll(context.TODO(), knockmapi.CommitCommitAllParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Result) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.Members.List(context.TODO(), knockmapi.MemberListParams{}) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) } /v1/api_keys/exchange: post: @@ -14232,24 +17903,24 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.APIKeys.Exchange(context.TODO(), knockmapi.APIKeyExchangeParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.APIKey) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.APIKeys.Exchange(context.TODO(), knockmapi.APIKeyExchangeParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.APIKey) } /v1/broadcasts/{broadcast_key}: get: @@ -14336,28 +18007,28 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - broadcast, err := client.Broadcasts.Get( - context.TODO(), - "broadcast_key", - knockmapi.BroadcastGetParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", broadcast.Valid) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + broadcast, err := client.Broadcasts.Get( + context.TODO(), + "broadcast_key", + knockmapi.BroadcastGetParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", broadcast.Valid) } put: callbacks: {} @@ -14530,40 +18201,40 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Broadcasts.Upsert( - context.TODO(), - "broadcast_key", - knockmapi.BroadcastUpsertParams{ - Environment: "development", - Broadcast: knockmapi.BroadcastRequestParam{ - Name: "My Broadcast", - Steps: []knockmapi.BroadcastRequestStepUnionParam{knockmapi.BroadcastRequestStepUnionParam{ - OfWorkflowInAppFeedStep: &knockmapi.WorkflowInAppFeedStepParam{ - Ref: "channel_1", - Template: knockmapi.InAppFeedTemplateParam{ - MarkdownBody: "Hello **{{ recipient.name }}**", - }, - Type: knockmapi.WorkflowInAppFeedStepTypeChannel, - }, - }}, - }, - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Broadcast) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Broadcasts.Upsert( + context.TODO(), + "broadcast_key", + knockmapi.BroadcastUpsertParams{ + Environment: "development", + Broadcast: knockmapi.BroadcastRequestParam{ + Name: "My Broadcast", + Steps: []knockmapi.BroadcastRequestStepUnionParam{{ + OfWorkflowInAppFeedStep: &knockmapi.WorkflowInAppFeedStepParam{ + Ref: "channel_1", + Template: knockmapi.InAppFeedTemplateParam{ + MarkdownBody: "Hello **{{ recipient.name }}**", + }, + Type: knockmapi.WorkflowInAppFeedStepTypeChannel, + }, + }}, + }, + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Broadcast) } /v1/message_types: get: @@ -14717,24 +18388,91 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" + + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" + ) + + func main() { + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + page, err := client.MessageTypes.List(context.TODO(), knockmapi.MessageTypeListParams{ + Environment: "development", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", page) + } + /v1/channels/{channel_key}: + get: + callbacks: {} + description: Returns a channel with all environment-specific settings. Secret values in provider settings are obfuscated unless they are Liquid templates (e.g., `{{ vars.api_key }}`). + operationId: getChannel + parameters: + - description: The key of the channel to retrieve. + in: path + name: channel_key + required: true + schema: + type: string + x-struct: null + x-validate: null + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Channel' + description: OK + summary: Get a channel + tags: + - Channels + x-stainless-snippets: + typescript: |- + import KnockMgmt from '@knocklabs/mgmt'; + + const client = new KnockMgmt({ + serviceToken: process.env['KNOCK_SERVICE_TOKEN'], // This is the default and can be omitted + }); + + const channel = await client.channels.retrieve('channel_key'); + + console.log(channel.id); + python: |- + import os + from knock_mapi import KnockMgmt + + client = KnockMgmt( + service_token=os.environ.get("KNOCK_SERVICE_TOKEN"), # This is the default and can be omitted + ) + channel = client.channels.retrieve( + "channel_key", + ) + print(channel.id) + go: | + package main + + import ( + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - page, err := client.MessageTypes.List(context.TODO(), knockmapi.MessageTypeListParams{ - Environment: "development", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", page) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + channel, err := client.Channels.Get(context.TODO(), "channel_key") + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", channel.ID) } /v1/broadcasts/{broadcast_key}/cancel: put: @@ -14844,28 +18582,28 @@ paths: package main import ( - "context" - "fmt" + "context" + "fmt" - "github.com/knocklabs/knock-mgmt-go" - "github.com/knocklabs/knock-mgmt-go/option" + "github.com/knocklabs/knock-mgmt-go" + "github.com/knocklabs/knock-mgmt-go/option" ) func main() { - client := knockmapi.NewClient( - option.WithServiceToken("My Service Token"), - ) - response, err := client.Broadcasts.Cancel( - context.TODO(), - "broadcast_key", - knockmapi.BroadcastCancelParams{ - Environment: "development", - }, - ) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", response.Broadcast) + client := knockmapi.NewClient( + option.WithServiceToken("My Service Token"), + ) + response, err := client.Broadcasts.Cancel( + context.TODO(), + "broadcast_key", + knockmapi.BroadcastCancelParams{ + Environment: "development", + }, + ) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", response.Broadcast) } security: - BearerAuth: [] @@ -14893,3 +18631,5 @@ tags: name: Message types - description: Guides let you define in-app guides that can be displayed to users based on priority and other conditions. name: Guides + - description: Audiences define sets of users that can be targeted for notifications. + name: Audiences diff --git a/data/specs/mapi/stainless.yml b/data/specs/mapi/stainless.yml index 5265c7f0c..9c48bd0c7 100644 --- a/data/specs/mapi/stainless.yml +++ b/data/specs/mapi/stainless.yml @@ -9,7 +9,8 @@ targets: package_name: "@knocklabs/mgmt" production_repo: knocklabs/knock-mgmt-node publish: - npm: true + npm: + auth_method: oidc python: package_name: knock_mapi production_repo: knocklabs/knock-mgmt-python @@ -26,6 +27,15 @@ resources: $shared: models: page_info: "#/components/schemas/PageInfo" + message_type_boolean_field: "#/components/schemas/MessageTypeBooleanField" + message_type_button_field: "#/components/schemas/MessageTypeButtonField" + message_type_image_field: "#/components/schemas/MessageTypeImageField" + message_type_url_field: "#/components/schemas/MessageTypeUrlField" + message_type_json_field: "#/components/schemas/MessageTypeJsonField" + message_type_markdown_field: "#/components/schemas/MessageTypeMarkdownField" + message_type_multi_select_field: "#/components/schemas/MessageTypeMultiSelectField" + message_type_select_field: "#/components/schemas/MessageTypeSelectField" + message_type_textarea_field: "#/components/schemas/MessageTypeTextareaField" templates: models: chat_template: "#/components/schemas/ChatTemplate" @@ -100,6 +110,12 @@ resources: workflow_fetch_step: "#/components/schemas/WorkflowFetchStep" workflow_throttle_step: "#/components/schemas/WorkflowThrottleStep" workflow_trigger_workflow_step: "#/components/schemas/WorkflowTriggerWorkflowStep" + workflow_random_cohort_step: "#/components/schemas/WorkflowRandomCohortStep" + workflow_update_data_step: "#/components/schemas/WorkflowUpdateDataStep" + workflow_update_user_step: "#/components/schemas/WorkflowUpdateUserStep" + workflow_update_tenant_step: "#/components/schemas/WorkflowUpdateTenantStep" + workflow_update_object_step: "#/components/schemas/WorkflowUpdateObjectStep" + workflow_ai_agent_step: "#/components/schemas/WorkflowAIAgentStep" methods: list: endpoint: get /v1/workflows @@ -144,9 +160,16 @@ resources: channel_group_rule: "#/components/schemas/ChannelGroupRule" methods: list: get /v1/channel_groups + retrieve: + endpoint: get /v1/channel_groups/{channel_group_key} + upsert: + endpoint: put /v1/channel_groups/{channel_group_key} + delete: + endpoint: delete /v1/channel_groups/{channel_group_key} channels: models: channel: "#/components/schemas/Channel" + channel_environment_settings: "#/components/schemas/ChannelEnvironmentSettings" chat_channel_settings: "#/components/schemas/ChatChannelSettings" email_channel_settings: "#/components/schemas/EmailChannelSettings" push_channel_settings: "#/components/schemas/PushChannelSettings" @@ -154,6 +177,15 @@ resources: in_app_feed_channel_settings: "#/components/schemas/InAppFeedChannelSettings" methods: list: get /v1/channels + retrieve: get /v1/channels/{channel_key} + members: + models: + member: "#/components/schemas/Member" + member_user: "#/components/schemas/MemberUser" + methods: + list: get /v1/members + retrieve: get /v1/members/{id} + delete: delete /v1/members/{id} environments: models: environment: "#/components/schemas/Environment" @@ -165,6 +197,7 @@ resources: variable: "#/components/schemas/Variable" methods: list: get /v1/variables + retrieve: get /v1/variables/{key} guides: models: guide: "#/components/schemas/Guide" @@ -202,6 +235,18 @@ resources: retrieve: get /v1/broadcasts/{broadcast_key} upsert: put /v1/broadcasts/{broadcast_key} cancel: put /v1/broadcasts/{broadcast_key}/cancel + audiences: + models: + audience: "#/components/schemas/Audience" + dynamic_audience: "#/components/schemas/DynamicAudience" + static_audience: "#/components/schemas/StaticAudience" + audience_condition: "#/components/schemas/AudienceCondition" + methods: + archive: delete /v1/audiences/{audience_key} + retrieve: get /v1/audiences/{audience_key} + upsert: put /v1/audiences/{audience_key} + validate: put /v1/audiences/{audience_key}/validate + list: get /v1/audiences settings: disable_mock_tests: true license: Apache-2.0 diff --git a/data/types.ts b/data/types.ts index 37aa3a2ae..b319d122b 100644 --- a/data/types.ts +++ b/data/types.ts @@ -4,6 +4,7 @@ export type SidebarPage = { title: string; path?: string; isBeta?: boolean; + isLegacy?: boolean; }; export type SidebarSubsection = { @@ -17,6 +18,7 @@ export type SidebarSection = { slug: string; desc?: string; isBeta?: boolean; + isLegacy?: boolean; pages: | SidebarPage[] | SidebarSubsection[] @@ -28,6 +30,7 @@ export type SidebarContent = { title: string; slug: string; isBeta?: boolean; + isLegacy?: boolean; pages?: SidebarContent[]; sidebarMenuDefaultOpen?: boolean; parentSection?: SidebarContent; diff --git a/lib/analytics.js b/lib/analytics.js index be667c301..6b226ec74 100644 --- a/lib/analytics.js +++ b/lib/analytics.js @@ -2,11 +2,15 @@ import * as snippet from "@segment/snippet"; import Script from "next/script"; export function track(eventName, attrs = {}) { - window?.analytics?.track(eventName, attrs); + if (typeof window?.analytics?.track === "function") { + window.analytics.track(eventName, attrs); + } } export function page(pageName, properties = {}) { - window?.analytics?.page(pageName, properties); + if (typeof window?.analytics?.page === "function") { + window.analytics.page(pageName, properties); + } } export const SEGMENT_WRITE_KEY = process.env.NEXT_PUBLIC_ANALYTICS_WRITE_KEY; diff --git a/lib/content.server.ts b/lib/content.server.ts index ad0d45e03..0d1ae2ad6 100644 --- a/lib/content.server.ts +++ b/lib/content.server.ts @@ -1,16 +1,9 @@ import fs from "fs"; import path from "path"; -import algoliasearch from "algoliasearch"; -import type { FrontMatter, DocsSearchItem } from "../types"; export const CONTENT_DIR = "content/"; export const DOCS_FILE_EXTENSIONS = [".mdx", ".md"]; -/** - * This is to index our .md and .mdx file content. - * API/mAPI reference content is indexed at script/indexApisForSearch.ts. - */ - export const getAllFilesInDir = ( directory: string, files: string[] = [], @@ -31,48 +24,5 @@ export const getAllFilesInDir = ( }; export function makeIdFromPath(resourcePath) { - return resourcePath.replace(/\.mdx?$/, "").replace("/index", ""); -} - -export async function generateAlgoliaIndex(frontmatter: FrontMatter) { - const algoliaAppId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID ?? ""; - const algoliaAdminApiKey = process.env.ALGOLIA_ADMIN_API_KEY ?? ""; - const algoliaIndexName = process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME ?? ""; - - if (algoliaAppId && algoliaAdminApiKey && algoliaIndexName) { - const client = algoliasearch(algoliaAppId, algoliaAdminApiKey); - const index = client.initIndex(algoliaIndexName); - - try { - // Notes: - // Algolia recommends saving objects in batches because of efficiency. - // Our markdown processor doesn't provide a callback to subscribe to that - // gets called after finishing with all elements. - // - // Given we only have ~40 items to be indexed right now, we are just saving - // entries one by one. - const object: DocsSearchItem = { - // The path to the page will be the identifier in Algolia. - objectID: frontmatter.id, - path: frontmatter.id, - title: frontmatter.title, - section: frontmatter.section, - // Once we add tags are added to pages, Algolia records - // will be updated with them, so we can enhance the search experience - tags: frontmatter.tags || [], - // Saving a content page, not an API endpoint - contentType: "document", - // Saving to the pages index - index: "pages", - }; - - await index.saveObject(object); - } catch (e) { - console.error(e); - } - } else { - console.info( - "Algolia configuration variables not present. Skipping indexing.", - ); - } + return resourcePath.replace(/\.mdx?$/, "").replace(/\/index$/, ""); } diff --git a/lib/content.ts b/lib/content.ts index 9557a04d8..42832f4ea 100644 --- a/lib/content.ts +++ b/lib/content.ts @@ -104,6 +104,22 @@ export function depthFirstSidebarInfo( }; } +// Finds a page in sidebar content, searching through empty-slug groups +// that serve as visual-only sidebar groupings without affecting URL paths. +const findInSidebarContent = ( + content: (SidebarSection | SidebarPage | SidebarContent)[], + predicate: (s: SidebarSection | SidebarPage | SidebarContent) => boolean, +): SidebarSection | SidebarPage | SidebarContent | undefined => { + for (const item of content) { + if (predicate(item)) return item; + if (item.slug === "" && "pages" in item && item.pages) { + const found = item.pages.find(predicate); + if (found) return found; + } + } + return undefined; +}; + // Returns the breadcrumbs and adjacent pages in the sidebar given a path export const getSidebarInfo = ( paths: string[], @@ -138,7 +154,8 @@ export const getSidebarInfo = ( let breadcrumbPath = path + `/${slug}`; // Match the full path to handle sidebar content that's not nested - const section = sidebarContent.find( + const section = findInSidebarContent( + sidebarContent, (s) => s.slug === breadcrumbPath || s.slug === `/${slug}`, ); diff --git a/lib/mdxComponents.tsx b/lib/mdxComponents.tsx index 584761b5e..ea26793ab 100644 --- a/lib/mdxComponents.tsx +++ b/lib/mdxComponents.tsx @@ -1,4 +1,4 @@ -import Image from "next/image"; +import NextImage from "next/image"; import { CodeBlock } from "../components/ui/CodeBlock"; import { Callout } from "../components/ui/Callout"; import MultiLangCodeBlock from "../components/ui/MultiLangCodeBlock"; @@ -41,13 +41,35 @@ import { ContentActions } from "@/components/ui/ContentActions"; import FeaturesMatrix from "@/components/ui/FeaturesMatrix"; import { PreTextDiagram } from "@/components/ui/PreTextDiagram"; +const Image = ({ + className, + border = true, + ...props +}: React.ComponentProps & { border?: boolean }) => ( +
      + +
      +); + export const MDX_COMPONENTS = { pre: (props) => , h2: (props) => , h3: (props) => , h4: (props) => , code: (props) => ( - + {props.children} ), diff --git a/next.config.js b/next.config.js index 1466632b2..41bbde68e 100644 --- a/next.config.js +++ b/next.config.js @@ -71,6 +71,21 @@ const nextConfig = { async redirects() { return [ + { + source: "/developer-tools/mcp-server", + destination: "/ai/mcp-server", + permanent: true, + }, + { + source: "/developer-tools/skills", + destination: "/ai/skills", + permanent: true, + }, + { + source: "/ai/cli-and-skills", + destination: "/ai/skills", + permanent: true, + }, { source: "/integrations", destination: "/integrations/overview", @@ -187,6 +202,16 @@ const nextConfig = { destination: "/send-and-manage-data/tenants", permanent: true, }, + { + source: "/concepts/tenants", + destination: "/multi-tenancy/overview", + permanent: true, + }, + { + source: "/preferences/tenant-preferences", + destination: "/multi-tenancy/per-tenant-preferences", + permanent: true, + }, { source: "/send-notifications/workflow-functions", destination: "/designing-workflows/overview", diff --git a/package.json b/package.json index 3da0302d4..1e8b5b26c 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,11 @@ "generate-llms": "yarn run open-api-to-md && tsx scripts/generateApiMarkdown.ts && tsx scripts/generateLlmsTxt.ts", "generate-reference-md": "tsx scripts/generateApiMarkdown.ts", "index-apis": "tsx scripts/indexApisForSearch.ts", + "index-docs": "tsx scripts/indexDocsForSearch.ts", "open-api-to-md": "bash scripts/openApiToMd.sh", "split-specs": "tsx scripts/splitOpenApiSpec.ts", "predev": "yarn split-specs && yarn generate-llms", - "prebuild": "yarn split-specs && yarn generate-llms && yarn index-apis" + "prebuild": "yarn split-specs && yarn generate-llms && yarn index-docs && yarn index-apis" }, "dependencies": { "@algolia/autocomplete-js": "^1.6.3", @@ -59,7 +60,7 @@ "lodash": "^4.17.21", "lucide-react": "^0.525.0", "mdast-util-to-hast": "13.2.1", - "next": "^16.1.4", + "next": "^16.2.3", "next-mdx-remote": "^6.0.0", "next-remote-refresh": "^0.10.1", "next-seo": "^5.4.0", diff --git a/pages/[...slug].tsx b/pages/[...slug].tsx index 00d485c78..c66cd4c52 100644 --- a/pages/[...slug].tsx +++ b/pages/[...slug].tsx @@ -14,7 +14,6 @@ import { CONTENT_DIR, DOCS_FILE_EXTENSIONS, makeIdFromPath, - generateAlgoliaIndex, } from "../lib/content.server"; import eventPayload from "../data/code/sources/eventPayload"; import datadogDashboardJson from "../content/integrations/extensions/datadog_dashboard.json"; @@ -96,9 +95,6 @@ export async function getStaticProps({ params: { slug } }) { // Extend frontmatter mdxSource.frontmatter.id = makeIdFromPath(slug.join(sep)); - // Index page in algolia - await generateAlgoliaIndex(mdxSource.frontmatter); - return { props: { source: mdxSource, sourcePath, typedocs } }; } diff --git a/pages/index.tsx b/pages/index.tsx index f582d1132..ac504b298 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -221,7 +221,7 @@ export default function Home() { diff --git a/public/images/ai/knock-agent.png b/public/images/ai/knock-agent.png new file mode 100644 index 000000000..714bab9ec Binary files /dev/null and b/public/images/ai/knock-agent.png differ diff --git a/public/images/designing-workflows/agent-function.png b/public/images/designing-workflows/agent-function.png new file mode 100644 index 000000000..040f681bd Binary files /dev/null and b/public/images/designing-workflows/agent-function.png differ diff --git a/public/images/functions/data/audience/in-workflow.png b/public/images/functions/data/audience/in-workflow.png new file mode 100644 index 000000000..62677d932 Binary files /dev/null and b/public/images/functions/data/audience/in-workflow.png differ diff --git a/public/images/functions/data/audience/logging-add-to-success.png b/public/images/functions/data/audience/logging-add-to-success.png new file mode 100644 index 000000000..e494c3167 Binary files /dev/null and b/public/images/functions/data/audience/logging-add-to-success.png differ diff --git a/public/images/functions/data/audience/logging-remove-no-op.png b/public/images/functions/data/audience/logging-remove-no-op.png new file mode 100644 index 000000000..6a350bc8f Binary files /dev/null and b/public/images/functions/data/audience/logging-remove-no-op.png differ diff --git a/public/images/functions/data/blob-mode.png b/public/images/functions/data/blob-mode.png new file mode 100644 index 000000000..51a53c68b Binary files /dev/null and b/public/images/functions/data/blob-mode.png differ diff --git a/public/images/functions/data/nested-keys.png b/public/images/functions/data/nested-keys.png new file mode 100644 index 000000000..ddc6b6787 Binary files /dev/null and b/public/images/functions/data/nested-keys.png differ diff --git a/public/images/functions/data/object/logging-upsert-object.png b/public/images/functions/data/object/logging-upsert-object.png new file mode 100644 index 000000000..6997dae73 Binary files /dev/null and b/public/images/functions/data/object/logging-upsert-object.png differ diff --git a/public/images/functions/data/object/overview.png b/public/images/functions/data/object/overview.png new file mode 100644 index 000000000..2d079dd97 Binary files /dev/null and b/public/images/functions/data/object/overview.png differ diff --git a/public/images/functions/data/object/templated-id.png b/public/images/functions/data/object/templated-id.png new file mode 100644 index 000000000..aaec02ce1 Binary files /dev/null and b/public/images/functions/data/object/templated-id.png differ diff --git a/public/images/functions/data/templated-data.png b/public/images/functions/data/templated-data.png new file mode 100644 index 000000000..d78fa5bb0 Binary files /dev/null and b/public/images/functions/data/templated-data.png differ diff --git a/public/images/functions/data/tenant/logging-update-tenant.png b/public/images/functions/data/tenant/logging-update-tenant.png new file mode 100644 index 000000000..243f72078 Binary files /dev/null and b/public/images/functions/data/tenant/logging-update-tenant.png differ diff --git a/public/images/functions/data/tenant/overview.png b/public/images/functions/data/tenant/overview.png new file mode 100644 index 000000000..0d8bf0180 Binary files /dev/null and b/public/images/functions/data/tenant/overview.png differ diff --git a/public/images/functions/data/tenant/templated-id.png b/public/images/functions/data/tenant/templated-id.png new file mode 100644 index 000000000..aeba1e297 Binary files /dev/null and b/public/images/functions/data/tenant/templated-id.png differ diff --git a/public/images/functions/data/user/logging-update-user.png b/public/images/functions/data/user/logging-update-user.png new file mode 100644 index 000000000..acb177c1e Binary files /dev/null and b/public/images/functions/data/user/logging-update-user.png differ diff --git a/public/images/functions/data/user/overview.png b/public/images/functions/data/user/overview.png new file mode 100644 index 000000000..4f4583e15 Binary files /dev/null and b/public/images/functions/data/user/overview.png differ diff --git a/public/images/functions/data/user/templated-id.png b/public/images/functions/data/user/templated-id.png new file mode 100644 index 000000000..4ac424e68 Binary files /dev/null and b/public/images/functions/data/user/templated-id.png differ diff --git a/public/images/functions/data/workflow-data/logging-update-workflow-data.png b/public/images/functions/data/workflow-data/logging-update-workflow-data.png new file mode 100644 index 000000000..4d95319f1 Binary files /dev/null and b/public/images/functions/data/workflow-data/logging-update-workflow-data.png differ diff --git a/public/images/functions/data/workflow-data/overview.png b/public/images/functions/data/workflow-data/overview.png new file mode 100644 index 000000000..740823d9d Binary files /dev/null and b/public/images/functions/data/workflow-data/overview.png differ diff --git a/public/images/in-app-ui/Guides_ToolbarEx.png b/public/images/in-app-ui/Guides_ToolbarEx.png new file mode 100644 index 000000000..19a930b31 Binary files /dev/null and b/public/images/in-app-ui/Guides_ToolbarEx.png differ diff --git a/public/images/in-app-ui/Guides_ToolbarSettings.png b/public/images/in-app-ui/Guides_ToolbarSettings.png new file mode 100644 index 000000000..706ff1fc9 Binary files /dev/null and b/public/images/in-app-ui/Guides_ToolbarSettings.png differ diff --git a/public/images/integrations/sources/clerk/clerk-add-webhook-endpoint.png b/public/images/integrations/sources/clerk/clerk-add-webhook-endpoint.png new file mode 100644 index 000000000..ca425cd35 Binary files /dev/null and b/public/images/integrations/sources/clerk/clerk-add-webhook-endpoint.png differ diff --git a/public/images/integrations/sources/clerk/clerk-signing-secret.png b/public/images/integrations/sources/clerk/clerk-signing-secret.png new file mode 100644 index 000000000..8668f34d2 Binary files /dev/null and b/public/images/integrations/sources/clerk/clerk-signing-secret.png differ diff --git a/public/images/integrations/sources/clerk/configure-default-mappings.png b/public/images/integrations/sources/clerk/configure-default-mappings.png new file mode 100644 index 000000000..b6bc54b71 Binary files /dev/null and b/public/images/integrations/sources/clerk/configure-default-mappings.png differ diff --git a/public/images/integrations/sources/clerk/copy-webhook-url.png b/public/images/integrations/sources/clerk/copy-webhook-url.png new file mode 100644 index 000000000..64ae4a66d Binary files /dev/null and b/public/images/integrations/sources/clerk/copy-webhook-url.png differ diff --git a/public/images/integrations/sources/clerk/select-clerk-template.png b/public/images/integrations/sources/clerk/select-clerk-template.png new file mode 100644 index 000000000..1dfea17c3 Binary files /dev/null and b/public/images/integrations/sources/clerk/select-clerk-template.png differ diff --git a/public/images/integrations/sources/custom/choose-event-type-path.png b/public/images/integrations/sources/custom/choose-event-type-path.png new file mode 100644 index 000000000..1ba5de4b7 Binary files /dev/null and b/public/images/integrations/sources/custom/choose-event-type-path.png differ diff --git a/public/images/integrations/sources/custom/configure-default-mappings.png b/public/images/integrations/sources/custom/configure-default-mappings.png new file mode 100644 index 000000000..390f71926 Binary files /dev/null and b/public/images/integrations/sources/custom/configure-default-mappings.png differ diff --git a/public/images/integrations/sources/custom/copy-webhook-url.png b/public/images/integrations/sources/custom/copy-webhook-url.png new file mode 100644 index 000000000..52c19d302 Binary files /dev/null and b/public/images/integrations/sources/custom/copy-webhook-url.png differ diff --git a/public/images/integrations/sources/custom/enforce-idempotency.png b/public/images/integrations/sources/custom/enforce-idempotency.png new file mode 100644 index 000000000..e6552a57a Binary files /dev/null and b/public/images/integrations/sources/custom/enforce-idempotency.png differ diff --git a/public/images/integrations/sources/custom/enforce-verification.png b/public/images/integrations/sources/custom/enforce-verification.png new file mode 100644 index 000000000..438e6b782 Binary files /dev/null and b/public/images/integrations/sources/custom/enforce-verification.png differ diff --git a/public/images/integrations/sources/custom/event-action-mapping.png b/public/images/integrations/sources/custom/event-action-mapping.png new file mode 100644 index 000000000..371fc45de Binary files /dev/null and b/public/images/integrations/sources/custom/event-action-mapping.png differ diff --git a/public/images/integrations/sources/custom/preprocess-script.png b/public/images/integrations/sources/custom/preprocess-script.png new file mode 100644 index 000000000..43f254423 Binary files /dev/null and b/public/images/integrations/sources/custom/preprocess-script.png differ diff --git a/public/images/integrations/sources/event-action-mapping.png b/public/images/integrations/sources/event-action-mapping.png new file mode 100644 index 000000000..80a58e001 Binary files /dev/null and b/public/images/integrations/sources/event-action-mapping.png differ diff --git a/public/images/integrations/sources/posthog/configure-default-mappings.png b/public/images/integrations/sources/posthog/configure-default-mappings.png new file mode 100644 index 000000000..3c535556e Binary files /dev/null and b/public/images/integrations/sources/posthog/configure-default-mappings.png differ diff --git a/public/images/integrations/sources/posthog/copy-webhook-url.png b/public/images/integrations/sources/posthog/copy-webhook-url.png new file mode 100644 index 000000000..b7f306b6d Binary files /dev/null and b/public/images/integrations/sources/posthog/copy-webhook-url.png differ diff --git a/public/images/integrations/sources/posthog/posthog-configure-destination.png b/public/images/integrations/sources/posthog/posthog-configure-destination.png new file mode 100644 index 000000000..aae1455c7 Binary files /dev/null and b/public/images/integrations/sources/posthog/posthog-configure-destination.png differ diff --git a/public/images/integrations/sources/posthog/posthog-new-destination.png b/public/images/integrations/sources/posthog/posthog-new-destination.png new file mode 100644 index 000000000..c863e2115 Binary files /dev/null and b/public/images/integrations/sources/posthog/posthog-new-destination.png differ diff --git a/public/images/integrations/sources/rudderstack/configure-default-mappings.png b/public/images/integrations/sources/rudderstack/configure-default-mappings.png new file mode 100644 index 000000000..b2e36e34d Binary files /dev/null and b/public/images/integrations/sources/rudderstack/configure-default-mappings.png differ diff --git a/public/images/integrations/sources/rudderstack/configure-webhook-url.png b/public/images/integrations/sources/rudderstack/configure-webhook-url.png new file mode 100644 index 000000000..aba541a26 Binary files /dev/null and b/public/images/integrations/sources/rudderstack/configure-webhook-url.png differ diff --git a/public/images/integrations/sources/rudderstack/connect-to-source.png b/public/images/integrations/sources/rudderstack/connect-to-source.png new file mode 100644 index 000000000..c09b1da0e Binary files /dev/null and b/public/images/integrations/sources/rudderstack/connect-to-source.png differ diff --git a/public/images/integrations/sources/rudderstack/copy-webhook-url.png b/public/images/integrations/sources/rudderstack/copy-webhook-url.png new file mode 100644 index 000000000..5505b2c4c Binary files /dev/null and b/public/images/integrations/sources/rudderstack/copy-webhook-url.png differ diff --git a/public/images/integrations/sources/rudderstack/name-destination.png b/public/images/integrations/sources/rudderstack/name-destination.png new file mode 100644 index 000000000..5f29cbbe0 Binary files /dev/null and b/public/images/integrations/sources/rudderstack/name-destination.png differ diff --git a/public/images/integrations/sources/rudderstack/search-webhook-destination.png b/public/images/integrations/sources/rudderstack/search-webhook-destination.png new file mode 100644 index 000000000..ae5907073 Binary files /dev/null and b/public/images/integrations/sources/rudderstack/search-webhook-destination.png differ diff --git a/public/images/integrations/sources/segment/configure-default-mappings.png b/public/images/integrations/sources/segment/configure-default-mappings.png new file mode 100644 index 000000000..f35c4936c Binary files /dev/null and b/public/images/integrations/sources/segment/configure-default-mappings.png differ diff --git a/public/images/integrations/sources/segment/copy-webhook-url.png b/public/images/integrations/sources/segment/copy-webhook-url.png new file mode 100644 index 000000000..c26220d1d Binary files /dev/null and b/public/images/integrations/sources/segment/copy-webhook-url.png differ diff --git a/public/images/integrations/sources/segment/segment-map-fields.png b/public/images/integrations/sources/segment/segment-map-fields.png new file mode 100644 index 000000000..687e05bda Binary files /dev/null and b/public/images/integrations/sources/segment/segment-map-fields.png differ diff --git a/public/images/integrations/sources/segment/segment-shared-secret.png b/public/images/integrations/sources/segment/segment-shared-secret.png new file mode 100644 index 000000000..ca364bcfe Binary files /dev/null and b/public/images/integrations/sources/segment/segment-shared-secret.png differ diff --git a/public/images/integrations/sources/source-env-config.png b/public/images/integrations/sources/source-env-config.png index 090573232..0ad9fb7d4 100644 Binary files a/public/images/integrations/sources/source-env-config.png and b/public/images/integrations/sources/source-env-config.png differ diff --git a/public/images/integrations/sources/sources-location.png b/public/images/integrations/sources/sources-location.png index 0bb95a881..db3e9f7e1 100644 Binary files a/public/images/integrations/sources/sources-location.png and b/public/images/integrations/sources/sources-location.png differ diff --git a/public/images/integrations/sources/stripe/configure-default-mappings.png b/public/images/integrations/sources/stripe/configure-default-mappings.png new file mode 100644 index 000000000..26ec12f69 Binary files /dev/null and b/public/images/integrations/sources/stripe/configure-default-mappings.png differ diff --git a/public/images/integrations/sources/stripe/copy-webhook-url.png b/public/images/integrations/sources/stripe/copy-webhook-url.png new file mode 100644 index 000000000..50eae08fd Binary files /dev/null and b/public/images/integrations/sources/stripe/copy-webhook-url.png differ diff --git a/public/images/integrations/sources/stripe/stripe-choose-destination.png b/public/images/integrations/sources/stripe/stripe-choose-destination.png new file mode 100644 index 000000000..1299024c5 Binary files /dev/null and b/public/images/integrations/sources/stripe/stripe-choose-destination.png differ diff --git a/public/images/integrations/sources/stripe/stripe-configure-destination.png b/public/images/integrations/sources/stripe/stripe-configure-destination.png new file mode 100644 index 000000000..f8b105aaf Binary files /dev/null and b/public/images/integrations/sources/stripe/stripe-configure-destination.png differ diff --git a/public/images/integrations/sources/stripe/stripe-open-developers.png b/public/images/integrations/sources/stripe/stripe-open-developers.png new file mode 100644 index 000000000..5a2166eb2 Binary files /dev/null and b/public/images/integrations/sources/stripe/stripe-open-developers.png differ diff --git a/public/images/integrations/sources/stripe/stripe-select-events.png b/public/images/integrations/sources/stripe/stripe-select-events.png new file mode 100644 index 000000000..70cfaa0e7 Binary files /dev/null and b/public/images/integrations/sources/stripe/stripe-select-events.png differ diff --git a/public/images/integrations/sources/stripe/stripe-signing-secret.png b/public/images/integrations/sources/stripe/stripe-signing-secret.png new file mode 100644 index 000000000..260f149a7 Binary files /dev/null and b/public/images/integrations/sources/stripe/stripe-signing-secret.png differ diff --git a/public/images/integrations/sources/stripe/stripe-trigger-test-events.png b/public/images/integrations/sources/stripe/stripe-trigger-test-events.png new file mode 100644 index 000000000..15ee768a3 Binary files /dev/null and b/public/images/integrations/sources/stripe/stripe-trigger-test-events.png differ diff --git a/public/images/integrations/sources/stripe/stripe-webhooks-page.png b/public/images/integrations/sources/stripe/stripe-webhooks-page.png new file mode 100644 index 000000000..d9147af51 Binary files /dev/null and b/public/images/integrations/sources/stripe/stripe-webhooks-page.png differ diff --git a/public/images/integrations/sources/supabase/configure-default-mappings.png b/public/images/integrations/sources/supabase/configure-default-mappings.png new file mode 100644 index 000000000..9c60c8c25 Binary files /dev/null and b/public/images/integrations/sources/supabase/configure-default-mappings.png differ diff --git a/public/images/integrations/sources/supabase/copy-webhook-url.png b/public/images/integrations/sources/supabase/copy-webhook-url.png new file mode 100644 index 000000000..923c0ac3a Binary files /dev/null and b/public/images/integrations/sources/supabase/copy-webhook-url.png differ diff --git a/public/images/integrations/sources/supabase/supabase-create-webhook-events.png b/public/images/integrations/sources/supabase/supabase-create-webhook-events.png new file mode 100644 index 000000000..40bc7a76e Binary files /dev/null and b/public/images/integrations/sources/supabase/supabase-create-webhook-events.png differ diff --git a/public/images/integrations/sources/supabase/supabase-create-webhook-url.png b/public/images/integrations/sources/supabase/supabase-create-webhook-url.png new file mode 100644 index 000000000..54abf7e52 Binary files /dev/null and b/public/images/integrations/sources/supabase/supabase-create-webhook-url.png differ diff --git a/public/images/integrations/sources/supabase/supabase-database-webhooks.png b/public/images/integrations/sources/supabase/supabase-database-webhooks.png new file mode 100644 index 000000000..2d956a244 Binary files /dev/null and b/public/images/integrations/sources/supabase/supabase-database-webhooks.png differ diff --git a/public/images/integrations/sources/workos/configure-default-mappings.png b/public/images/integrations/sources/workos/configure-default-mappings.png new file mode 100644 index 000000000..426772698 Binary files /dev/null and b/public/images/integrations/sources/workos/configure-default-mappings.png differ diff --git a/public/images/integrations/sources/workos/copy-webhook-url.png b/public/images/integrations/sources/workos/copy-webhook-url.png new file mode 100644 index 000000000..8d47391a7 Binary files /dev/null and b/public/images/integrations/sources/workos/copy-webhook-url.png differ diff --git a/public/images/integrations/sources/workos/workos-add-webhook-endpoint.png b/public/images/integrations/sources/workos/workos-add-webhook-endpoint.png new file mode 100644 index 000000000..5d5be511f Binary files /dev/null and b/public/images/integrations/sources/workos/workos-add-webhook-endpoint.png differ diff --git a/public/images/integrations/sources/workos/workos-signing-secret.png b/public/images/integrations/sources/workos/workos-signing-secret.png new file mode 100644 index 000000000..5fd22b1a2 Binary files /dev/null and b/public/images/integrations/sources/workos/workos-signing-secret.png differ diff --git a/scripts/indexApisForSearch.ts b/scripts/indexApisForSearch.ts index df0b191fe..c631df365 100644 --- a/scripts/indexApisForSearch.ts +++ b/scripts/indexApisForSearch.ts @@ -3,13 +3,19 @@ import { readOpenApiSpec, readStainlessSpec, } from "@/lib/openApiSpec"; -import { RESOURCE_ORDER as API_RESOURCE_ORDER } from "@/data/sidebars/apiOverviewSidebar"; -import { RESOURCE_ORDER as MAPI_RESOURCE_ORDER } from "@/data/sidebars/mapiOverviewSidebar"; +import { + API_REFERENCE_OVERVIEW_CONTENT, + RESOURCE_ORDER as API_RESOURCE_ORDER, +} from "@/data/sidebars/apiOverviewSidebar"; +import { + MAPI_REFERENCE_OVERVIEW_CONTENT, + RESOURCE_ORDER as MAPI_RESOURCE_ORDER, +} from "@/data/sidebars/mapiOverviewSidebar"; import algoliasearch from "algoliasearch"; import { resolveEndpointFromMethod } from "@/components/ui/ApiReference/helpers"; import JSONPointer from "jsonpointer"; import { loadEnvConfig } from "@next/env"; -import type { DocsSearchItem, EndpointSearchItem } from "@/types"; +import type { EndpointSearchItem, EnhancedDocsSearchItem } from "@/types"; import { readFile } from "fs/promises"; import path from "path"; @@ -29,8 +35,17 @@ const algoliaEndpointIndexName = let indexCount = 0; let endpointCount = 0; +const MAX_CONTENT_LENGTH = 2000; + +interface StaticContentHeading { + level: number; + title: string; + slug: string; + content: string; +} + async function validateSearchObject( - object: DocsSearchItem | EndpointSearchItem, + object: EnhancedDocsSearchItem | EndpointSearchItem, ) { if (object.path.startsWith("/")) { return Promise.reject( @@ -45,11 +60,16 @@ async function validateSearchObject( * So we're pretty good for now and can either expand that limit or * save objects in chunks. These batches work for now! */ -let pagesToSave: DocsSearchItem[] = []; -async function queuePage(object: DocsSearchItem) { +let pagesToSave: EnhancedDocsSearchItem[] = []; +async function queuePage(object: EnhancedDocsSearchItem) { await validateSearchObject(object); // Keep this in for logging purposes - console.log("Indexing page:", object.title, object.path); + console.log( + "Indexing page:", + object.title, + object.path, + `(${object.content.length} content chars)`, + ); indexCount++; pagesToSave.push(object); return; @@ -65,6 +85,115 @@ async function queueEndpoint(object: EndpointSearchItem) { return; } +function buildApiPageSearchItem({ + objectID, + path, + title, + pageTitle = title, + section, + content = "", + tags = [], + headingLevel = 0, + isPageLevel = true, +}: { + objectID: string; + path: string; + title: string; + pageTitle?: string; + section: string; + content?: string; + tags?: string[]; + headingLevel?: number; + isPageLevel?: boolean; +}): EnhancedDocsSearchItem { + return { + objectID, + path, + title, + pageTitle, + content: content.slice(0, MAX_CONTENT_LENGTH), + section, + tags, + headingLevel, + contentType: "api-reference", + index: "pages", + isPageLevel, + }; +} + +function extractTextContent(mdxContent: string): string { + let content = mdxContent; + + content = content.replace(/^---[\s\S]*?---\n*/g, ""); + content = content.replace(/```[^\n]*\n([\s\S]*?)```/g, "$1"); + content = content.replace(/`([^`]+)`/g, "$1"); + content = content.replace(/!\[([^\]]*)\]\([^)]+\)/g, ""); + content = content.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1"); + content = content.replace(/\{\/\*[\s\S]*?\*\/\}/g, ""); + content = content.replace(/<\/?[A-Za-z][^>]*>/g, " "); + content = content.replace(/[{}[\](),"]/g, " "); + content = content.replace(/\s+/g, " "); + + return content.trim(); +} + +function getOverviewSectionTitle(name: "api" | "mapi", path: string): string { + const overviewContent = + name === "api" + ? API_REFERENCE_OVERVIEW_CONTENT + : MAPI_REFERENCE_OVERVIEW_CONTENT; + const pathWithoutOverview = path.replace(/^\/overview/, "") || "/"; + const page = overviewContent[0]?.pages.find( + ({ slug }) => slug === pathWithoutOverview, + ); + + return page?.title ?? "Overview"; +} + +function slugify(text: string): string { + return text + .trim() + .toLowerCase() + .replace(/[^\w\s-]/g, "") + .replace(/\s+/g, "-") + .replace(/-+/g, "-"); +} + +function extractHeadings(content: string): StaticContentHeading[] { + const headingRegex = /^(#{2,3})\s+(.+)$/gm; + const matches: Array<{ index: number; level: number; title: string }> = []; + const headings: StaticContentHeading[] = []; + let match; + + while ((match = headingRegex.exec(content)) !== null) { + matches.push({ + index: match.index, + level: match[1].length, + title: match[2].trim(), + }); + } + + for (let index = 0; index < matches.length; index++) { + const current = matches[index]; + const next = matches[index + 1]; + const contentStart = + current.index + `${"#".repeat(current.level)} ${current.title}`.length; + const contentEnd = next ? next.index : content.length; + const cleanContent = extractTextContent( + content.slice(contentStart, contentEnd), + ); + + headings.push({ + level: current.level, + title: current.title, + slug: slugify(current.title), + content: cleanContent, + }); + } + + return headings; +} + async function indexResource({ apiName, openApiSpec, @@ -99,34 +228,37 @@ async function indexResource({ const sectionName = section + " > " + resourceName; - const resourceObject: DocsSearchItem = { - // The path to the page will be the identifier in Algolia. + const resourceObject = buildApiPageSearchItem({ objectID: `page-${staticName}-${basePath}`, path: basePath, title: resourceName, section, - tags: [], - contentType: "api-reference", - index: "pages", - }; + content: resourceName, + }); await queuePage(resourceObject); // Methods like get, post, put, delete - Object.keys(methods).forEach(async (methodName) => { + for (const methodName of Object.keys(methods)) { const method = methods[methodName]; const [methodType, endpoint] = resolveEndpointFromMethod(method); const openApiOperation = openApiSpec.paths?.[endpoint]?.[methodType]; const title = openApiOperation?.summary; const methodUrl = `${basePath}/${methodName}`; - const docsSearchItem: DocsSearchItem = { + const docsSearchItem = buildApiPageSearchItem({ objectID: `page-${staticName}-${methodUrl}`, title, path: methodUrl, section: sectionName, - tags: [], - contentType: "api-reference", - index: "pages", - }; + tags: openApiOperation?.tags ?? [], + content: [ + openApiOperation?.summary, + openApiOperation?.description, + methodType?.toUpperCase(), + endpoint, + ] + .filter(Boolean) + .join(" "), + }); await queuePage(docsSearchItem); const formattedApiName = apiName === "api" ? "API" : "mAPI"; @@ -140,28 +272,28 @@ async function indexResource({ index: "endpoints", }; await queueEndpoint(endpointSearchItem); - }); + } // Handle the schemas - Object.keys(models).forEach(async (modelName) => { + for (const modelName of Object.keys(models)) { const modelRef = models[modelName]; const modelUrl = `${basePath}/schemas/${modelName}`; const schema = JSONPointer.get(openApiSpec, modelRef.replace("#", "")); const title = schema?.title ?? modelName; - const modelObject: DocsSearchItem = { + const modelObject = buildApiPageSearchItem({ objectID: `page-${staticName}-${modelUrl}`, title, path: modelUrl, section: sectionName + " > " + "Object definitions", - tags: [], - contentType: "api-reference", - index: "pages", - }; + content: [schema?.title, schema?.description, modelName] + .filter(Boolean) + .join(" "), + }); await queuePage(modelObject); - }); + } // Subresources like BulkOperations - Object.keys(resource.subresources ?? {}).forEach(async (subresourceName) => { + for (const subresourceName of Object.keys(resource.subresources ?? {})) { if (!resource.subresources) return; const subresource = resource.subresources[subresourceName]; // Recursively index the subresource @@ -173,7 +305,7 @@ async function indexResource({ pathPrefix: basePath, section: sectionName, }); - }); + } } async function indexApi(name: "api" | "mapi") { @@ -209,13 +341,19 @@ async function indexStaticContent(name: "api" | "mapi") { // Find all sections in the static content and index them function extractSectionInfo(content: string) { - const sections: Array<{ title?: string; path?: string }> = []; - const parser = new RegExp(//g); + const sections: Array<{ + title: string; + path: string; + content: string; + headings: StaticContentHeading[]; + }> = []; + const parser = new RegExp(/]*)>([\s\S]*?)<\/Section>/g); const attributeParser = /(\w+)="([^"]*)"/g; let match; while ((match = parser.exec(content)) !== null) { const attributes = match[1]; + const body = match[2]; const sectionInfo: { title?: string; path?: string } = {}; let attrMatch; @@ -225,25 +363,45 @@ async function indexStaticContent(name: "api" | "mapi") { if (name === "path") sectionInfo.path = value; } - sections.push(sectionInfo); + if (!sectionInfo.path) continue; + + sections.push({ + path: sectionInfo.path, + title: + sectionInfo.title ?? getOverviewSectionTitle(name, sectionInfo.path), + content: extractTextContent(body), + headings: extractHeadings(body), + }); } return sections; } const sections = extractSectionInfo(staticContent); for (const section of sections) { - const { title, path } = section; - if (!title || !path) continue; - const sectionObject: DocsSearchItem = { + const { title, path, content, headings } = section; + const sectionObject = buildApiPageSearchItem({ objectID: `page-section-${name}-reference${path}`, path: `${name}-reference${path}`, title, section: name === "api" ? "API Reference" : "mAPI Reference", - tags: [], - contentType: "api-reference", - index: "pages", - }; + content, + }); await queuePage(sectionObject); + + for (const heading of headings) { + const headingPath = `${name}-reference${path}#${heading.slug}`; + const headingObject = buildApiPageSearchItem({ + objectID: `heading-section-${headingPath}`, + path: headingPath, + title: heading.title, + pageTitle: title, + section: name === "api" ? "API Reference" : "mAPI Reference", + content: heading.content, + headingLevel: heading.level, + isPageLevel: false, + }); + await queuePage(headingObject); + } } return staticContent; } diff --git a/scripts/indexDocsForSearch.ts b/scripts/indexDocsForSearch.ts new file mode 100644 index 000000000..e87856c26 --- /dev/null +++ b/scripts/indexDocsForSearch.ts @@ -0,0 +1,407 @@ +import fs from "fs"; +import path from "path"; +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkFrontmatter from "remark-frontmatter"; +import yaml from "yaml"; +import algoliasearch from "algoliasearch"; +import { loadEnvConfig } from "@next/env"; +import type { EnhancedDocsSearchItem } from "@/types"; + +// Load Next.js environment variables +const projectDir = process.cwd(); +loadEnvConfig(projectDir); + +const algoliaAppId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID ?? ""; +const algoliaAdminApiKey = process.env.ALGOLIA_ADMIN_API_KEY ?? ""; +const algoliaPagesIndexName = process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME ?? ""; + +const CONTENT_DIR = path.join(projectDir, "content"); +const DOCS_FILE_EXTENSIONS = [".mdx", ".md"]; + +// Maximum content length per record (in characters) +// Algolia recommends keeping records small for better performance +const MAX_CONTENT_LENGTH = 2000; + +// Keep count of indexed items +let pageCount = 0; +let headingCount = 0; + +interface Heading { + level: number; + title: string; + slug: string; + content: string; +} + +interface Frontmatter { + title: string; + description?: string; + tags?: string[]; + section: string; +} + +/** + * Recursively get all files in a directory with specific extensions + */ +function getAllFilesInDir( + directory: string, + files: string[] = [], + extensions?: string[], +): string[] { + fs.readdirSync(directory).forEach((file) => { + const subpath = path.join(directory, file); + if (fs.lstatSync(subpath).isDirectory()) { + getAllFilesInDir(subpath, files, extensions); + } else { + if (!extensions || extensions.includes(path.extname(subpath))) { + files.push(subpath); + } + } + }); + + return files; +} + +/** + * Parse frontmatter from markdown content using remark + */ +async function parseFrontmatter( + markdownContent: string, +): Promise { + const file = await unified() + .use(remarkParse) + .use(remarkFrontmatter, ["yaml"]) + .parse(markdownContent); + + const yamlNode = file.children.find( + (node): node is { type: "yaml"; value: string } => node.type === "yaml", + ); + if (!yamlNode) return null; + return yaml.parse(yamlNode.value); +} + +/** + * Create a URL-friendly slug from a heading title + */ +function slugify(text: string): string { + return text + .toLowerCase() + .replace(/[^\w\s-]/g, "") // Remove non-word characters except spaces and hyphens + .replace(/\s+/g, "-") // Replace spaces with hyphens + .replace(/-+/g, "-") // Replace multiple hyphens with single + .trim(); +} + +/** + * Remove frontmatter from markdown content + */ +function removeFrontmatter(content: string): string { + // Match YAML frontmatter at the start of the file + const frontmatterRegex = /^---[\s\S]*?---\n*/; + return content.replace(frontmatterRegex, ""); +} + +/** + * Extract plain text from markdown content + * Removes JSX components, imports, code blocks, and other non-text elements + */ +function extractTextContent(mdxContent: string): string { + let content = mdxContent; + + // Remove import statements + content = content.replace(/^import\s+.*$/gm, ""); + + // Remove export statements + content = content.replace(/^export\s+.*$/gm, ""); + + // Remove code blocks (fenced) + content = content.replace(/```[\s\S]*?```/g, ""); + + // Remove inline code + content = content.replace(/`[^`]+`/g, ""); + + // Remove JSX components (self-closing and with children) + content = content.replace(/<[A-Z][^>]*\/>/g, ""); // Self-closing like + content = content.replace(/<[A-Z][^>]*>[\s\S]*?<\/[A-Z][^>]*>/g, ""); // With children + + // Remove HTML-style components + content = content.replace(/<[a-z][^>]*>[\s\S]*?<\/[a-z][^>]*>/g, ""); + + // Remove remaining HTML/JSX tags + content = content.replace(/<[^>]+>/g, ""); + + // Remove markdown images (must come before links since images contain link syntax) + content = content.replace(/!\[([^\]]*)\]\([^)]+\)/g, ""); + + // Remove markdown links but keep the text + content = content.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1"); + + // Remove markdown emphasis markers + content = content.replace(/\*\*([^*]+)\*\*/g, "$1"); // Bold + content = content.replace(/\*([^*]+)\*/g, "$1"); // Italic + content = content.replace(/__([^_]+)__/g, "$1"); // Bold + content = content.replace(/_([^_]+)_/g, "$1"); // Italic + + // Remove heading markers + content = content.replace(/^#{1,6}\s+/gm, ""); + + // Remove horizontal rules + content = content.replace(/^[-*_]{3,}$/gm, ""); + + // Remove list markers + content = content.replace(/^\s*[-*+]\s+/gm, ""); + content = content.replace(/^\s*\d+\.\s+/gm, ""); + + // Remove blockquote markers + content = content.replace(/^\s*>\s*/gm, ""); + + // Normalize whitespace + content = content.replace(/\n{3,}/g, "\n\n"); // Multiple newlines to double + content = content.replace(/[ \t]+/g, " "); // Multiple spaces to single + + return content.trim(); +} + +/** + * Extract headings with their content from markdown + */ +function extractHeadings(mdxContent: string): Heading[] { + // Remove frontmatter first + const contentWithoutFrontmatter = removeFrontmatter(mdxContent); + + const headingRegex = /^(#{2,3})\s+(.+)$/gm; + const headings: Heading[] = []; + let match; + + const matches: Array<{ index: number; level: number; title: string }> = []; + + while ((match = headingRegex.exec(contentWithoutFrontmatter)) !== null) { + matches.push({ + index: match.index, + level: match[1].length, + title: match[2].trim(), + }); + } + + // Extract content for each heading + for (let i = 0; i < matches.length; i++) { + const current = matches[i]; + const next = matches[i + 1]; + + const contentStart = + current.index + `${"#".repeat(current.level)} ${current.title}`.length; + const contentEnd = next ? next.index : contentWithoutFrontmatter.length; + const rawContent = contentWithoutFrontmatter.slice( + contentStart, + contentEnd, + ); + + const cleanContent = extractTextContent(rawContent); + + // Only include headings with meaningful content + if (cleanContent.length > 20) { + headings.push({ + level: current.level, + title: current.title, + slug: slugify(current.title), + content: cleanContent.slice(0, MAX_CONTENT_LENGTH), + }); + } + } + + return headings; +} + +/** + * Get the intro content (content before the first heading) + */ +function getIntroContent(mdxContent: string): string { + const contentWithoutFrontmatter = removeFrontmatter(mdxContent); + + // Find the first H2 or H3 heading + const firstHeadingMatch = contentWithoutFrontmatter.match(/^#{2,3}\s+/m); + + if (firstHeadingMatch && firstHeadingMatch.index !== undefined) { + const introRaw = contentWithoutFrontmatter.slice( + 0, + firstHeadingMatch.index, + ); + return extractTextContent(introRaw).slice(0, MAX_CONTENT_LENGTH); + } + + // No headings found, use all content + return extractTextContent(contentWithoutFrontmatter).slice( + 0, + MAX_CONTENT_LENGTH, + ); +} + +/** + * Convert file path to URL path + */ +function filePathToUrlPath(filePath: string): string { + return filePath + .replace(CONTENT_DIR, "") + .replace(/\.mdx?$/, "") + .replace(/\/index$/, "") // Only remove /index at end of path + .replace(/^\//, ""); // Remove leading slash for objectID +} + +/** + * Queue of items to save to Algolia + */ +const itemsToSave: EnhancedDocsSearchItem[] = []; + +async function queueItem(item: EnhancedDocsSearchItem) { + // Validate path doesn't start with / + if (item.path.startsWith("/")) { + console.error(`Path may not start with "/". Violating path: ${item.path}`); + return; + } + + console.log( + `Indexing ${item.isPageLevel ? "page" : "heading"}: ${item.title} -> ${ + item.path + }`, + ); + itemsToSave.push(item); +} + +/** + * Process a single MDX file and create search records + */ +async function processFile(filePath: string): Promise { + // Skip special directories + if ( + filePath.includes("/__mapi-reference/") || + filePath.includes("/__api-reference/") || + filePath.includes("/__cli/") + ) { + return; + } + + const content = fs.readFileSync(filePath, "utf-8"); + const frontmatter = await parseFrontmatter(content); + + if (!frontmatter || !frontmatter.title || !frontmatter.section) { + console.warn(`Skipping ${filePath}: missing required frontmatter`); + return; + } + + const urlPath = filePathToUrlPath(filePath); + + // Create page-level record + const introContent = getIntroContent(content); + const pageRecord: EnhancedDocsSearchItem = { + objectID: `page-${urlPath}`, + path: urlPath, + title: frontmatter.title, + pageTitle: frontmatter.title, + description: frontmatter.description, + content: introContent, + section: frontmatter.section, + tags: frontmatter.tags || [], + headingLevel: 0, + contentType: "document", + index: "pages", + isPageLevel: true, + }; + await queueItem(pageRecord); + pageCount++; + + // Extract and create heading-level records + const headings = extractHeadings(content); + for (const heading of headings) { + const headingPath = `${urlPath}#${heading.slug}`; + const headingRecord: EnhancedDocsSearchItem = { + objectID: `heading-${headingPath}`, + path: headingPath, + title: heading.title, + pageTitle: frontmatter.title, + content: heading.content, + section: frontmatter.section, + tags: frontmatter.tags || [], + headingLevel: heading.level, + contentType: "document", + index: "pages", + isPageLevel: false, + }; + await queueItem(headingRecord); + headingCount++; + } +} + +/** + * Main entry point + */ +async function main() { + console.log("πŸ” Starting docs search indexing...\n"); + + let skipIndexing = false; + + // Check for required environment variables + if (!algoliaAppId || !algoliaAdminApiKey || !algoliaPagesIndexName) { + const missing: string[] = []; + if (!algoliaAppId) missing.push("NEXT_PUBLIC_ALGOLIA_APP_ID"); + if (!algoliaAdminApiKey) missing.push("ALGOLIA_ADMIN_API_KEY"); + if (!algoliaPagesIndexName) missing.push("NEXT_PUBLIC_ALGOLIA_INDEX_NAME"); + + console.warn( + "Missing Algolia environment variables. Continuing with script but skipping actual indexing.\n\nMissing: " + + missing.join(", "), + ); + skipIndexing = true; + } + + // Get all MDX/MD files + const files = getAllFilesInDir(CONTENT_DIR, [], DOCS_FILE_EXTENSIONS); + console.log(`Found ${files.length} content files to process\n`); + + // Process each file + for (const file of files) { + try { + await processFile(file); + } catch (error) { + console.error(`Error processing ${file}:`, error); + } + } + + console.log("\nπŸ“Š Indexing summary:"); + console.log(` Pages indexed: ${pageCount}`); + console.log(` Headings indexed: ${headingCount}`); + console.log(` Total records: ${itemsToSave.length}`); + + // Save to Algolia + if (!skipIndexing && itemsToSave.length > 0) { + console.log("\nπŸ“€ Uploading to Algolia..."); + + const client = algoliasearch(algoliaAppId, algoliaAdminApiKey); + const index = client.initIndex(algoliaPagesIndexName); + + // Save objects in batches (Algolia recommends batches of 1000) + const BATCH_SIZE = 1000; + for (let i = 0; i < itemsToSave.length; i += BATCH_SIZE) { + const batch = itemsToSave.slice(i, i + BATCH_SIZE); + await index.saveObjects(batch); + console.log( + ` Saved batch ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil( + itemsToSave.length / BATCH_SIZE, + )}`, + ); + } + + console.log("\nβœ… Successfully indexed docs for search!"); + } else if (skipIndexing) { + console.log( + "\n⚠️ Completed processing, but skipped Algolia upload due to missing environment variables.", + ); + } + + process.exit(0); +} + +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/tailwind.config.js b/tailwind.config.js index 7b80a8e0e..22b2b9848 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,7 @@ module.exports = { "./layouts/**/*.{js,ts,jsx,tsx}", "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", + "./content/**/*.{md,mdx}", ], theme: { extend: { diff --git a/typedocs/client/feed.mdx b/typedocs/client/feed.mdx index 2476e7552..d59021b3b 100644 --- a/typedocs/client/feed.mdx +++ b/typedocs/client/feed.mdx @@ -17,7 +17,7 @@ Connects the feed instance to the realtime socket so that any new items publishe **Example**: ```javascript title="Connecting to a realtime stream" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -125,7 +125,7 @@ Binds an event handler to be invoked when the event is triggered. **Example**: ```javascript title="Listening to items being received" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -151,7 +151,7 @@ Programmatically access the current `FeedStoreState`. ### `markAllAsSeen` -Marks all of the items in the store optimistically as seen and performs a server-side request to mark **all items on the feed in the current scope** as seen. Broadcasts a `items:all_seen` event. +Marks all of the items in the store optimistically as seen and performs a server-side request to mark **all items on the feed in the current scope** as seen. Broadcasts an `items.all_seen` event. **Please note**: this operation is deferred and may take some time to process all items in the feed. @@ -191,7 +191,7 @@ Removes the `seen` status on the item or items given. Will perform the operation ### `markAllAsRead` -Marks all of the items in the store optimistically as read and performs a server-side request to mark **all items on the feed in the current scope** as read. Broadcasts a `items:all_read` event. +Marks all of the items in the store optimistically as read and performs a server-side request to mark **all items on the feed in the current scope** as read. Broadcasts an `items.all_read` event. **Please note**: this operation is deferred and may take some time to process all items in the feed. @@ -231,7 +231,7 @@ Removes the `read` status on the item or items given. Will perform the operation ### `markAllAsArchived` -Marks all of the items in the store optimistically as archived and performs a server-side request to mark **all items on the feed in the current scope** as archived. Broadcasts a `items:all_archived` event. +Marks all of the items in the store optimistically as archived and performs a server-side request to mark **all items on the feed in the current scope** as archived. Broadcasts an `items.all_archived` event. **Please note**: this operation is deferred and may take some time to process all items in the feed. @@ -239,7 +239,7 @@ Marks all of the items in the store optimistically as archived and performs a se ### `markAsArchived` -Sets the `archived` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. Broadcasts a `items:archived` event. +Sets the `archived` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. Broadcasts an `items.archived` event. **Parameters**: @@ -271,7 +271,7 @@ Removes the `archived` status on the item or items given. Will perform the opera ### `markAsInteracted` -Sets the `interacted` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. Broadcasts an `items:interacted` event. +Sets the `interacted` status on the item or items given. Will perform the operation optimistically, including updating the current `metadata` in the `FeedStoreState`. Broadcasts an `items.interacted` event. **Parameters**: diff --git a/typedocs/client/guide-client.mdx b/typedocs/client/guide-client.mdx index 82901cf9c..847628a4e 100644 --- a/typedocs/client/guide-client.mdx +++ b/typedocs/client/guide-client.mdx @@ -81,7 +81,7 @@ Fetches guides from the API based on the current targeting parameters and option **Example**: ```javascript title="Fetching guides" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); diff --git a/typedocs/client/knock.mdx b/typedocs/client/knock.mdx index cc0b7aabe..a2cd26863 100644 --- a/typedocs/client/knock.mdx +++ b/typedocs/client/knock.mdx @@ -18,7 +18,7 @@ The top-level `Knock` class, used to interact with a client instance. ## Example ```javascript -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); ``` @@ -42,7 +42,7 @@ Optionally a feed can be initialized with a default set of `FeedClientOptions` w **Example**: ```javascript -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -63,7 +63,7 @@ Returns a `UserClient` instance to interact with the users API for the current, **Example**: ```javascript -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -82,7 +82,7 @@ Returns a `Preferences` instance to interact with the preferences API for the cu **Example**: ```javascript -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -128,7 +128,7 @@ Authenticates the current user and creates a new Knock session. **Example**: ```javascript -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id, name: "Knock" }, user.knockUserToken); diff --git a/typedocs/client/message-client.mdx b/typedocs/client/message-client.mdx index d992e1036..958752298 100644 --- a/typedocs/client/message-client.mdx +++ b/typedocs/client/message-client.mdx @@ -37,7 +37,7 @@ Retrieves a specific message by its ID. **Example**: ```javascript title="Getting a message" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -74,7 +74,7 @@ Updates the engagement status of a message. For the "interacted" status, additio **Example**: ```javascript title="Updating message status" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -114,7 +114,7 @@ Removes an engagement status from a message. Note: Cannot remove "interacted" st **Example**: ```javascript title="Removing message status" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -161,7 +161,7 @@ Updates the engagement status of multiple messages in a single request. **Example**: ```javascript title="Batch updating message statuses" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -209,7 +209,7 @@ Updates the engagement status of all messages in a specific channel. **Example**: ```javascript title="Bulk updating all messages in a channel" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); diff --git a/typedocs/client/ms-teams-client.mdx b/typedocs/client/ms-teams-client.mdx index 4f6316c29..704dc9322 100644 --- a/typedocs/client/ms-teams-client.mdx +++ b/typedocs/client/ms-teams-client.mdx @@ -42,7 +42,7 @@ Checks the authentication status for a Microsoft Teams channel integration. **Example**: ```javascript title="Checking Microsoft Teams authentication" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -103,7 +103,7 @@ Retrieves available Microsoft Teams teams for the authenticated integration. **Example**: ```javascript title="Getting Microsoft Teams teams" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -164,7 +164,7 @@ Retrieves available Microsoft Teams channels for a specific team. **Example**: ```javascript title="Getting Microsoft Teams channels" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -200,7 +200,7 @@ Revokes the access token for a Microsoft Teams integration, effectively disconne **Example**: ```javascript title="Revoking Microsoft Teams access token" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); diff --git a/typedocs/client/object-client.mdx b/typedocs/client/object-client.mdx index 81c18c67e..73604b015 100644 --- a/typedocs/client/object-client.mdx +++ b/typedocs/client/object-client.mdx @@ -57,7 +57,7 @@ Retrieves channel data for a specific object in a collection. **Example**: ```javascript title="Getting object channel data" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -119,7 +119,7 @@ Sets or updates channel data for a specific object in a collection. **Example**: ```javascript title="Setting object channel data" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); diff --git a/typedocs/client/preferences.mdx b/typedocs/client/preferences.mdx index a55672088..536ab4caf 100644 --- a/typedocs/client/preferences.mdx +++ b/typedocs/client/preferences.mdx @@ -11,7 +11,7 @@ Retrieves a preference set for the authenticated user by calling the [get prefer **Example**: ```javascript title="Getting preferences for the user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -38,7 +38,7 @@ Updates the authenticated users preferences by calling the [set preferences endp **Example**: ```javascript title="Setting preferences for the user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); diff --git a/typedocs/client/slack-client.mdx b/typedocs/client/slack-client.mdx index b9796098f..7469680c7 100644 --- a/typedocs/client/slack-client.mdx +++ b/typedocs/client/slack-client.mdx @@ -42,7 +42,7 @@ Checks the authentication status for a Slack channel integration. **Example**: ```javascript title="Checking Slack authentication" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -108,7 +108,7 @@ Retrieves available Slack channels for the authenticated integration. **Example**: ```javascript title="Getting Slack channels" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -144,7 +144,7 @@ Revokes the access token for a Slack integration, effectively disconnecting it. **Example**: ```javascript title="Revoking Slack access token" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); diff --git a/typedocs/client/user-client.mdx b/typedocs/client/user-client.mdx index b08ab11b2..72f538915 100644 --- a/typedocs/client/user-client.mdx +++ b/typedocs/client/user-client.mdx @@ -11,7 +11,7 @@ Retrieves the current, authenticated user by calling the [get user endpoint](/ap **Example**: ```javascript title="Getting the authenticated user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -38,7 +38,7 @@ Identifies a user by calling the [identify user endpoint](/api-reference/users/u **Example**: ```javascript title="Identifying a user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -59,7 +59,7 @@ Retrieves all preferences for the authenticated user by calling the [get prefere **Example**: ```javascript title="Getting preferences for the user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -91,7 +91,7 @@ Retrieves a preference set for the authenticated user by calling the [get prefer **Example**: ```javascript title="Getting a preference set for the user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -120,7 +120,7 @@ Updates the authenticated user's preferences by calling the [set preferences end **Example**: ```javascript title="Setting preferences for the user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -154,7 +154,7 @@ Retrieves channel data for the authenticated user by calling the [get channel da **Example**: ```javascript title="Getting channel data for the user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); @@ -188,7 +188,7 @@ Updates the channel data for the current user by calling the [set channel data e **Example**: ```javascript title="Setting channel data for the user" -import { Knock } from "@knocklabs/client"; +import Knock from "@knocklabs/client"; const knock = new Knock(process.env.KNOCK_PUBLIC_API_KEY); knock.authenticate({ id: user.id }); diff --git a/typedocs/react-core/hooks/use-guides.mdx b/typedocs/react-core/hooks/use-guides.mdx index 618ffc568..75ddc8cde 100644 --- a/typedocs/react-core/hooks/use-guides.mdx +++ b/typedocs/react-core/hooks/use-guides.mdx @@ -1,12 +1,29 @@ The `useGuides` hook returns an array of guides matching the provided filter criteria. It supports typing the guide step content to ensure type safety. +Starting in version 0.10.0 of `@knocklabs/react`, `useGuides` respects [throttling rules](/in-app-ui/guides/order-guides#guide-throttling) by default, similar to `useGuide`. + ## Parameters +The hook accepts two parameters: a filter object and an optional options object. + +### Filter object + + + +### Options object + + + @@ -70,3 +87,26 @@ const MyComponent = () => { ); }; ``` + +### Including throttled guides + +By default, `useGuides` respects throttling rules. To return all eligible guides regardless of throttling, pass `includeThrottled: true`: + +```tsx +import { useGuides } from "@knocklabs/react"; + +const MyComponent = () => { + const { guides } = useGuides( + { type: "changelog" }, + { includeThrottled: true }, + ); + + return ( +
      + {guides.map((guide) => ( +
      {guide.name}
      + ))} +
      + ); +}; +``` diff --git a/typedocs/react-core/hooks/use-ms-teams-auth.mdx b/typedocs/react-core/hooks/use-ms-teams-auth.mdx index 491e441e1..b7349a667 100644 --- a/typedocs/react-core/hooks/use-ms-teams-auth.mdx +++ b/typedocs/react-core/hooks/use-ms-teams-auth.mdx @@ -42,7 +42,7 @@ The following example demonstrates how to use the `useMsTeamsAuth` hook to manag ```tsx import { KnockMsTeamsProvider, useMsTeamsAuth } from "@knocklabs/react"; -const MsTeamsAuthButton = () => { +const MsTeamsAuthenticationButton = () => { const { buildMsTeamsAuthUrl, disconnectFromMsTeams } = useMsTeamsAuth( "graph-api-client-id", "https://example.com/callback", @@ -50,7 +50,7 @@ const MsTeamsAuthButton = () => { const handleConnect = () => { const authUrl = buildMsTeamsAuthUrl(); - window.location.href = authUrl; + window.open(authUrl, "msTeamsAuth"); }; return ( @@ -68,7 +68,7 @@ const App = () => ( knockMsTeamsChannelId="ms-teams-channel-id" tenantId="tenant-id" > - + ); ``` diff --git a/typedocs/react-core/hooks/use-slack-auth.mdx b/typedocs/react-core/hooks/use-slack-auth.mdx index e1afa865b..93b20e84e 100644 --- a/typedocs/react-core/hooks/use-slack-auth.mdx +++ b/typedocs/react-core/hooks/use-slack-auth.mdx @@ -47,7 +47,7 @@ The following example demonstrates how to use the `useSlackAuth` hook to manage ```tsx import { KnockSlackProvider, useSlackAuth } from "@knocklabs/react"; -const SlackAuthButton = () => { +const SlackAuthenticationButton = () => { const { buildSlackAuthUrl, disconnectFromSlack } = useSlackAuth( "slack-client-id", "https://example.com/callback", @@ -55,7 +55,7 @@ const SlackAuthButton = () => { const handleConnect = () => { const authUrl = buildSlackAuthUrl(); - window.location.href = authUrl; + window.open(authUrl, "slackAuth"); }; return ( @@ -71,7 +71,7 @@ const App = () => ( knockSlackChannelId="slack-channel-id" tenantId="tenant-id" > - + ); ``` diff --git a/types.ts b/types.ts index f3cb89ee6..71bc5ed0f 100644 --- a/types.ts +++ b/types.ts @@ -47,6 +47,24 @@ export type DocsSearchItem = { index: "pages" | "endpoints"; }; +// Enhanced search item type for improved Algolia indexing +// This extends the basic DocsSearchItem with content and heading information +export type EnhancedDocsSearchItem = { + objectID: string; // Unique ID (page-path or page-path#heading-slug) + path: string; // URL path (with optional anchor) + title: string; // Page title OR heading title + pageTitle: string; // Always the parent page title + description?: string; // From frontmatter (page-level only) + content: string; // Text content (truncated ~300-500 words) + section: string; // Top-level section (Concepts, Getting Started, etc.) + tags: string[]; // Tags from frontmatter + headingLevel: number; // 0 for page, 2 for H2, 3 for H3 + contentType: "document" | "api-reference"; + index: "pages" | "endpoints"; + // Ranking fields + isPageLevel: boolean; // True if this is a page-level record (not a heading) +}; + export type EndpointSearchItem = DocsSearchItem & { method: string; endpoint: string; diff --git a/yarn.lock b/yarn.lock index 8b18647f4..760037995 100644 --- a/yarn.lock +++ b/yarn.lock @@ -172,15 +172,15 @@ integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== "@babel/code-frame@^7.23.5": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== dependencies: - "@babel/helper-validator-identifier" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/helper-validator-identifier@^7.27.1": +"@babel/helper-validator-identifier@^7.28.5": version "7.28.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== @@ -203,9 +203,9 @@ "@jridgewell/trace-mapping" "0.3.9" "@emnapi/runtime@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.7.0.tgz#d7ef3832df8564fe5903bf0567aedbd19538ecbe" - integrity sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q== + version "1.9.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.9.1.tgz#115ff2a0d589865be6bd8e9d701e499c473f2a8d" + integrity sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA== dependencies: tslib "^2.4.0" @@ -553,9 +553,9 @@ integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== "@img/colour@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@img/colour/-/colour-1.0.0.tgz#d2fabb223455a793bf3bf9c70de3d28526aa8311" - integrity sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@img/colour/-/colour-1.1.0.tgz#b0c2c2fa661adf75effd6b4964497cd80010bb9d" + integrity sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ== "@img/sharp-darwin-arm64@0.34.5": version "0.34.5" @@ -895,50 +895,50 @@ dependencies: "@ndhoule/each" "^2.0.1" -"@next/env@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/env/-/env-16.1.4.tgz#1f5155b16bad9825432b5e398b83df687b7b86f9" - integrity sha512-gkrXnZyxPUy0Gg6SrPQPccbNVLSP3vmW8LU5dwEttEEC1RwDivk8w4O+sZIjFvPrSICXyhQDCG+y3VmjlJf+9A== - -"@next/swc-darwin-arm64@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.4.tgz#2d5ee68da80c9b822edd06caa360aef1917d0f37" - integrity sha512-T8atLKuvk13XQUdVLCv1ZzMPgLPW0+DWWbHSQXs0/3TjPrKNxTmUIhOEaoEyl3Z82k8h/gEtqyuoZGv6+Ugawg== - -"@next/swc-darwin-x64@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.4.tgz#2f8d4462f48d4cb3c927de1962ca7a7b2f8a5b03" - integrity sha512-AKC/qVjUGUQDSPI6gESTx0xOnOPQ5gttogNS3o6bA83yiaSZJek0Am5yXy82F1KcZCx3DdOwdGPZpQCluonuxg== - -"@next/swc-linux-arm64-gnu@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.4.tgz#79fecac25ad4a0ee1081110f4c8863b87e754943" - integrity sha512-POQ65+pnYOkZNdngWfMEt7r53bzWiKkVNbjpmCt1Zb3V6lxJNXSsjwRuTQ8P/kguxDC8LRkqaL3vvsFrce4dMQ== - -"@next/swc-linux-arm64-musl@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.4.tgz#e9a99b1ea9a68908c3d36a847a6fe367b4fc3855" - integrity sha512-3Wm0zGYVCs6qDFAiSSDL+Z+r46EdtCv/2l+UlIdMbAq9hPJBvGu/rZOeuvCaIUjbArkmXac8HnTyQPJFzFWA0Q== - -"@next/swc-linux-x64-gnu@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.4.tgz#4804de5f42ac8333e0049ab538473cbd996507f6" - integrity sha512-lWAYAezFinaJiD5Gv8HDidtsZdT3CDaCeqoPoJjeB57OqzvMajpIhlZFce5sCAH6VuX4mdkxCRqecCJFwfm2nQ== - -"@next/swc-linux-x64-musl@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.4.tgz#4aa01e59b0e0fd19ab493ee239e3904c42419ca6" - integrity sha512-fHaIpT7x4gA6VQbdEpYUXRGyge/YbRrkG6DXM60XiBqDM2g2NcrsQaIuj375egnGFkJow4RHacgBOEsHfGbiUw== - -"@next/swc-win32-arm64-msvc@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.4.tgz#67652a5c57889f44c11e145d49f777ac2e6cde58" - integrity sha512-MCrXxrTSE7jPN1NyXJr39E+aNFBrQZtO154LoCz7n99FuKqJDekgxipoodLNWdQP7/DZ5tKMc/efybx1l159hw== - -"@next/swc-win32-x64-msvc@16.1.4": - version "16.1.4" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.4.tgz#3c51597eb64a96b8fcade74ab3f21ef3ad278a33" - integrity sha512-JSVlm9MDhmTXw/sO2PE/MRj+G6XOSMZB+BcZ0a7d6KwVFZVpkHcb2okyoYFBaco6LeiL53BBklRlOrDDbOeE5w== +"@next/env@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/env/-/env-16.2.3.tgz#eda120ae25aa43b3ff9c0621f5fa6e10e46ef749" + integrity sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA== + +"@next/swc-darwin-arm64@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.3.tgz#ec4fea25a921dce0847a2b8d7df419ea49615172" + integrity sha512-u37KDKTKQ+OQLvY+z7SNXixwo4Q2/IAJFDzU1fYe66IbCE51aDSAzkNDkWmLN0yjTUh4BKBd+hb69jYn6qqqSg== + +"@next/swc-darwin-x64@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.3.tgz#de3d5281f8ca81ef23527d93e81229e6f85c4ec7" + integrity sha512-gHjL/qy6Q6CG3176FWbAKyKh9IfntKZTB3RY/YOJdDFpHGsUDXVH38U4mMNpHVGXmeYW4wj22dMp1lTfmu/bTQ== + +"@next/swc-linux-arm64-gnu@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.3.tgz#dbd85b17dd94e23a676084089b5b383bbf9d346c" + integrity sha512-U6vtblPtU/P14Y/b/n9ZY0GOxbbIhTFuaFR7F4/uMBidCi2nSdaOFhA0Go81L61Zd6527+yvuX44T4ksnf8T+Q== + +"@next/swc-linux-arm64-musl@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.3.tgz#a2361a6e741c64c8e6cac347631e4001150f1711" + integrity sha512-/YV0LgjHUmfhQpn9bVoGc4x4nan64pkhWR5wyEV8yCOfwwrH630KpvRg86olQHTwHIn1z59uh6JwKvHq1h4QEw== + +"@next/swc-linux-x64-gnu@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.3.tgz#d356deb1ae924d1e3a5071d64f5be0e3f1e916ac" + integrity sha512-/HiWEcp+WMZ7VajuiMEFGZ6cg0+aYZPqCJD3YJEfpVWQsKYSjXQG06vJP6F1rdA03COD9Fef4aODs3YxKx+RDQ== + +"@next/swc-linux-x64-musl@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.3.tgz#3b307a0691995a8fa323d32a83eb100e3ac03358" + integrity sha512-Kt44hGJfZSefebhk/7nIdivoDr3Ugp5+oNz9VvF3GUtfxutucUIHfIO0ZYO8QlOPDQloUVQn4NVC/9JvHRk9hw== + +"@next/swc-win32-arm64-msvc@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.3.tgz#eae5f6f105d0c855911821be74931f755761dc6d" + integrity sha512-O2NZ9ie3Tq6xj5Z5CSwBT3+aWAMW2PIZ4egUi9MaWLkwaehgtB7YZjPm+UpcNpKOme0IQuqDcor7BsW6QBiQBw== + +"@next/swc-win32-x64-msvc@16.2.3": + version "16.2.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.3.tgz#aff6de2107cb29c9e8f3242e43f432d00dbea0e0" + integrity sha512-Ibm29/GgB/ab5n7XKqlStkm54qqZE8v2FnijUPBgrd67FWrac45o/RsNlaOWjme/B5UqeWt/8KM4aWBwA1D2Kw== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -2058,9 +2058,9 @@ integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@types/debug@^4.0.0": - version "4.1.12" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" - integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + version "4.1.13" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.13.tgz#22d1cc9d542d3593caea764f974306ab36286ee7" + integrity sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw== dependencies: "@types/ms" "*" @@ -2318,7 +2318,12 @@ acorn-walk@^8.1.1: dependencies: acorn "^8.11.0" -acorn@^8.0.0, acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: +acorn@^8.0.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: version "8.15.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -2353,9 +2358,9 @@ ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.17.1: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + version "8.18.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc" + integrity sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A== dependencies: fast-deep-equal "^3.1.3" fast-uri "^3.0.1" @@ -2384,9 +2389,9 @@ algoliasearch@^4.13.0: "@algolia/transporter" "4.25.3" altcha-lib@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/altcha-lib/-/altcha-lib-1.3.0.tgz#0db5d92710c5b38ec1d0ebec7ebe24642e18d29e" - integrity sha512-PpFg/JPuR+Jiud7Vs54XSDqDxvylcp+0oDa/i1ARxBA/iKDqLeNlO8PorQbfuDTMVLYRypAa/2VDK3nbBTAu5A== + version "1.4.1" + resolved "https://registry.yarnpkg.com/altcha-lib/-/altcha-lib-1.4.1.tgz#1398f9f46d9e6941d1ddb1739f129f73cc708ce7" + integrity sha512-MAXP9tkQOA2SE9Gwoe3LAcZbcDpp3XzYc5GDVej/y3eMNaFG/eVnRY1/7SGFW0RPsViEjPf+hi5eANjuZrH1xA== ansi-regex@^5.0.1: version "5.0.1" @@ -2492,10 +2497,10 @@ baseline-browser-mapping@^2.8.19: resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz#947dc6f81778e0fa0424a2ab9ea09a3033e71109" integrity sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA== -baseline-browser-mapping@^2.8.3: - version "2.9.14" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz#3b6af0bc032445bca04de58caa9a87cfe921cbb3" - integrity sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg== +baseline-browser-mapping@^2.9.19: + version "2.10.13" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz#5a154cc4589193015a274e3d18319b0d76b9224e" + integrity sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw== binary-extensions@^2.0.0: version "2.3.0" @@ -2564,9 +2569,9 @@ camelize@^1.0.0: integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001751: - version "1.0.30001754" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz#7758299d9a72cce4e6b038788a15b12b44002759" - integrity sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg== + version "1.0.30001784" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz#bdf9733a0813ccfb5ab4d02f2127e62ee4c6b718" + integrity sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw== ccount@^2.0.0: version "2.0.1" @@ -2696,10 +2701,10 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -commander@^14.0.1: - version "14.0.2" - resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.2.tgz#b71fd37fe4069e4c3c7c13925252ada4eba14e8e" - integrity sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ== +commander@^14.0.2: + version "14.0.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.3.tgz#425d79b48f9af82fcd9e4fc1ea8af6c5ec07bbc2" + integrity sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw== commander@^4.0.0: version "4.1.1" @@ -2777,9 +2782,9 @@ debug@^4.0.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: ms "^2.1.3" decode-named-character-reference@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed" - integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== + version "1.3.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f" + integrity sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q== dependencies: character-entities "^2.0.0" @@ -3790,10 +3795,10 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inline-style-parser@0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.6.tgz#e6bbb5288a635f6284a6249ce754da55c4bd1ff5" - integrity sha512-gtGXVaBdl5mAes3rPcMedEBm12ibjt1kDMFfheul1wUAOVEJW60voNdMVzVkfLN06O7ZaD/rxhfKgtlgtTbMjg== +inline-style-parser@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" + integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== is-alphabetical@^1.0.0: version "1.0.4" @@ -4123,9 +4128,9 @@ mdast-util-find-and-replace@^3.0.0: unist-util-visit-parents "^6.0.0" mdast-util-from-markdown@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" - integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + version "2.0.3" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz#c95822b91aab75f18a4cbe8b2f51b873ed2cf0c7" + integrity sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q== dependencies: "@types/mdast" "^4.0.0" "@types/unist" "^3.0.0" @@ -4872,27 +4877,27 @@ next-seo@^5.4.0: resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-5.15.0.tgz#b1a90508599774982909ea44803323c6fb7b50f4" integrity sha512-LGbcY91yDKGMb7YI+28n3g+RuChUkt6pXNpa8FkfKkEmNiJkeRDEXTnnjVtwT9FmMhG6NH8qwHTelGrlYm9rgg== -next@^16.1.4: - version "16.1.4" - resolved "https://registry.yarnpkg.com/next/-/next-16.1.4.tgz#d024bace2d52a2bea1dec33149b8bbd0852632c5" - integrity sha512-gKSecROqisnV7Buen5BfjmXAm7Xlpx9o2ueVQRo5DxQcjC8d330dOM1xiGWc2k3Dcnz0In3VybyRPOsudwgiqQ== +next@^16.2.3: + version "16.2.3" + resolved "https://registry.yarnpkg.com/next/-/next-16.2.3.tgz#091b6565d46b3fb494fbb5c73d201171890787a5" + integrity sha512-9V3zV4oZFza3PVev5/poB9g0dEafVcgNyQ8eTRop8GvxZjV2G15FC5ARuG1eFD42QgeYkzJBJzHghNP8Ad9xtA== dependencies: - "@next/env" "16.1.4" + "@next/env" "16.2.3" "@swc/helpers" "0.5.15" - baseline-browser-mapping "^2.8.3" + baseline-browser-mapping "^2.9.19" caniuse-lite "^1.0.30001579" postcss "8.4.31" styled-jsx "5.1.6" optionalDependencies: - "@next/swc-darwin-arm64" "16.1.4" - "@next/swc-darwin-x64" "16.1.4" - "@next/swc-linux-arm64-gnu" "16.1.4" - "@next/swc-linux-arm64-musl" "16.1.4" - "@next/swc-linux-x64-gnu" "16.1.4" - "@next/swc-linux-x64-musl" "16.1.4" - "@next/swc-win32-arm64-msvc" "16.1.4" - "@next/swc-win32-x64-msvc" "16.1.4" - sharp "^0.34.4" + "@next/swc-darwin-arm64" "16.2.3" + "@next/swc-darwin-x64" "16.2.3" + "@next/swc-linux-arm64-gnu" "16.2.3" + "@next/swc-linux-arm64-musl" "16.2.3" + "@next/swc-linux-x64-gnu" "16.2.3" + "@next/swc-linux-x64-musl" "16.2.3" + "@next/swc-win32-arm64-msvc" "16.2.3" + "@next/swc-win32-x64-msvc" "16.2.3" + sharp "^0.34.5" node-domexception@1.0.0: version "1.0.0" @@ -5026,13 +5031,13 @@ openai@4.78.1: node-fetch "^2.6.7" openapi-to-md@^1.0.25: - version "1.0.27" - resolved "https://registry.yarnpkg.com/openapi-to-md/-/openapi-to-md-1.0.27.tgz#b7d4393287398c0432d220b9149135dbb47875c2" - integrity sha512-9X/KVQ6gsa5lzN0EiYGziDyTm8Lyw2cEOGROjlChXMjVD8vgGXgqcxjSZG/mNa2inUn5hfoHpnxWRsnDT8geBA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/openapi-to-md/-/openapi-to-md-1.1.1.tgz#bc2d3326950bf8ed35825e8dac1b62a34056c3c9" + integrity sha512-jQe/74++dnRD4DqBvc4dlgg6vx+HwIN++uQoKB+VKtS/xAvLpkdimlwvyAevK1U++1BgXmYm76rh7caFHoe2kQ== dependencies: - commander "^14.0.1" - swagger2openapi "*" - yaml "*" + commander "^14.0.2" + swagger2openapi "^7.0.8" + yaml "^2.8.2" optionator@^0.9.3: version "0.9.4" @@ -5772,11 +5777,11 @@ scheduler@^0.27.0: integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== semver@^7.3.7, semver@^7.7.3: - version "7.7.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" - integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== -sharp@^0.34.4: +sharp@^0.34.5: version "0.34.5" resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.5.tgz#b6f148e4b8c61f1797bde11a9d1cfebbae2c57b0" integrity sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg== @@ -5937,7 +5942,16 @@ streamdown@^2.0.1: unified "^11.0.5" unist-util-visit "^5.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5968,7 +5982,14 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -5988,18 +6009,18 @@ strip-json-comments@^3.1.1: integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== style-to-js@^1.0.0: - version "1.1.19" - resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.19.tgz#f3f671e74764c1e8eebfba95a9a672acf5c19011" - integrity sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ== + version "1.1.21" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d" + integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ== dependencies: - style-to-object "1.0.12" + style-to-object "1.0.14" -style-to-object@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.12.tgz#03a30e7f4e555bf50845f8b55c7d5bc2248e03f0" - integrity sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw== +style-to-object@1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611" + integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw== dependencies: - inline-style-parser "0.2.6" + inline-style-parser "0.2.7" styled-jsx@5.1.6: version "5.1.6" @@ -6033,7 +6054,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -swagger2openapi@*: +swagger2openapi@^7.0.8: version "7.0.8" resolved "https://registry.yarnpkg.com/swagger2openapi/-/swagger2openapi-7.0.8.tgz#12c88d5de776cb1cbba758994930f40ad0afac59" integrity sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g== @@ -6304,16 +6325,7 @@ unist-util-visit-parents@^6.0.0: "@types/unist" "^3.0.0" unist-util-is "^6.0.0" -unist-util-visit@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" - integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== - dependencies: - "@types/unist" "^3.0.0" - unist-util-is "^6.0.0" - unist-util-visit-parents "^6.0.0" - -unist-util-visit@^5.1.0: +unist-util-visit@^5.0.0, unist-util-visit@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468" integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg== @@ -6456,7 +6468,16 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -6494,20 +6515,15 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yaml@*, yaml@^2.4.5, yaml@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79" - integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw== - yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yaml@^2.0.0: - version "2.8.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5" - integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A== + version "1.10.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.3.tgz#76e407ed95c42684fb8e14641e5de62fe65bbcb3" + integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== + +yaml@^2.0.0, yaml@^2.4.5, yaml@^2.7.0, yaml@^2.8.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.3.tgz#a0d6bd2efb3dd03c59370223701834e60409bd7d" + integrity sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg== yargs-parser@^21.1.1: version "21.1.1"