{"id":21685,"date":"2026-08-15T16:00:33","date_gmt":"2026-08-15T14:00:33","guid":{"rendered":"https:\/\/www.juust.org\/?p=21685"},"modified":"2026-08-15T22:38:20","modified_gmt":"2026-08-15T20:38:20","slug":"langgraph-workflow-in-typescript","status":"publish","type":"post","link":"https:\/\/www.juust.org\/index.php\/langgraph-workflow-in-typescript\/2026\/08\/","title":{"rendered":"Orchestrated LangGraph Workflow in TypeScript"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Four Agents, One Graph<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><em>A state graph with explicit nodes, conditional edges, and a built-in retry gate \u2014 using the same lightweight agents from the previous articles.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every example so far in this series has used the same pattern: plain async functions calling the Chat Completions API in a <code>while<\/code> loop. No frameworks, no SDKs \u2014 just <code>fetch()<\/code>, a message list and plain functions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This example introduces <strong>LangGraph<\/strong>, a minimal framework from LangChain that models workflows as a <strong>state graph<\/strong>: nodes are the work, edges are the control flow, and the state object is passed between them. The workflow is the same SEO content pipeline you have seen before \u2014 research, write, review, publish \u2014 but expressed as a compileable, inspectable graph rather than a chain of async function calls.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Everything is on GitHub at <a href=\"https:\/\/github.com\/juustesout\/typescript-agent-example-langgraph\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/juustesout\/typescript-agent-example-langgraph<\/a> in the <code>TSLangGraph<\/code> folder.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. Installatie \u2014 The One Dependency<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Unlike the other examples in this series, LangGraph adds two npm packages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>npm install --save @langchain\/langgraph @langchain\/core\nnpm install --save-dev typescript @types\/node\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Everything else is the same setup: Node.js 18+, an OpenAI key, and DataForSEO + Jina + WordPress credentials in <code>.env<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>OPENAI_API_KEY=sk-proj-your-key-here\nDATAFORSEO_API_KEY=your-dataforseo-key\nJINA_API_KEY=your-jina-key\nWP_BASE_URL=https:\/\/your-site.com\nWP_USERNAME=your-wordpress-username\nWP_APPLICATION_PASSWORD=your-wordpress-password\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>npm run build\nnpm run demo \"AI sales agents for SMBs\"\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. Project Structure<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>TSLangGraph\/\n\u251c\u2500\u2500 src\/\n\u2502   \u251c\u2500\u2500 graph.ts    # LangGraph state, nodes, edges, and the compiled graph\n\u2502   \u2514\u2500\u2500 demo.ts     # CLI runner \u2014 invokes the graph and prints state\n\u251c\u2500\u2500 dist\/\n\u251c\u2500\u2500 package.json    # langchain\/langgraph + langchain\/core\n\u251c\u2500\u2500 tsconfig.json\n\u2514\u2500\u2500 .env\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Two source files, one graph. All the specialist agent logic \u2014 SEO research, writing, review, publishing \u2014 lives inside <code>graph.ts<\/code> as separate <strong>node functions<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. State \u2014 The Core of a LangGraph Workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every LangGraph workflow revolves around a single <strong>state object<\/strong> that passes through every node. Each node reads from the state and returns a partial update. The graph engine merges the updates and passes the new state to the next node.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>type WorkflowState = {\n  topic: string;                    \/\/ set by the caller\n  research?: { ... };               \/\/ set by researchNode\n  draft?: DraftArticle;             \/\/ set by writerNode\n  review?: ReviewResult;            \/\/ set by reviewNode\n  reviewAttempts: number;           \/\/ incremented on each revision loop\n  publishPayload?: PublishPayload;  \/\/ set by publisherNode\n  publishResult?: PublishResult;    \/\/ set by publisherNode\n};\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In LangGraph terms, this is declared via the <code>Annotation.Root<\/code> helper:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const WorkflowAnnotation = Annotation.Root({\n  topic: Annotation&lt;string&gt;,\n  research: Annotation&lt;any&gt;,\n  draft: Annotation&lt;any&gt;,\n  review: Annotation&lt;any&gt;,\n  reviewAttempts: Annotation&lt;number&gt;,\n  publishPayload: Annotation&lt;any&gt;,\n  publishResult: Annotation&lt;any&gt;,\n});\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each key in the annotation becomes a <strong>channel<\/strong> in the graph. Nodes can read any channel and write to any channel. The graph engine handles the merging.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. The Nodes \u2014 What Each Step Does<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each node is an async function that receives the current state and returns a partial state update. They are identical in logic to the standalone <code>runSeoResearch()<\/code>, <code>runWriter()<\/code>, <code>runReviewer()<\/code> and <code>runPublisher()<\/code> functions from the TShandoff\/TSworkflow examples \u2014 the only difference is the wrapper signature.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Research Node<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>async function researchNode(state: WorkflowState): Promise&lt;Partial&lt;WorkflowState&gt;&gt; {\n  return { research: await runSeoResearch(state.topic) };\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It reads <code>state.topic<\/code>, runs the full DataForSEO + Jina pipeline, and sets <code>state.research<\/code> with the brief. That is three lines for the node wrapper \u2014 the actual work is delegated to <code>runSeoResearch()<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Writer Node<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The writer node is the most interesting because it handles both the initial write and revision passes. It checks whether the state already contains a rejected review \u2014 if so, it passes the feedback to the model and rewrites instead of writing from scratch:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>async function writerNode(state: WorkflowState): Promise&lt;Partial&lt;WorkflowState&gt;&gt; {\n  if (!state.research) throw new Error(\"Research must happen before writing.\");\n\n  if (state.review &amp;&amp; !state.review.approved) {\n    \/\/ Revision pass: use the review feedback to rewrite\n    const revised = await callModelForJson(\n      buildAgent(WRITER_PROMPT, { temperature: 0.7 }),\n      `The draft was rejected. Rewrite using this feedback:\n       ${JSON.stringify({ research: state.research,\n         draft: state.draft, review: state.review })}`\n    );\n    return {\n      draft: { ... revised ... },\n      reviewAttempts: state.reviewAttempts + 1,\n    };\n  }\n\n  \/\/ First pass: write from research\n  return { draft: await runWriter(state.research) };\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The node returns <em>both<\/em> the updated draft and the incremented <code>reviewAttempts<\/code> counter \u2014 the graph engine merges both into the state.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Review Node<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>async function reviewNode(state: WorkflowState): Promise&lt;Partial&lt;WorkflowState&gt;&gt; {\n  if (!state.draft) throw new Error(\"Draft is missing before review.\");\n  return { review: await runReviewer(state.draft) };\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Simple gate: read the draft, produce a <code>ReviewResult<\/code> with <code>approved<\/code>, <code>score<\/code>, <code>issues<\/code> and <code>requiredChanges<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Publisher Node<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>async function publisherNode(state: WorkflowState): Promise&lt;Partial&lt;WorkflowState&gt;&gt; {\n  if (!state.draft) throw new Error(\"Draft is missing before publishing.\");\n  const publishPayload = await runPublisher(state.draft);\n  return {\n    publishPayload,\n    publishResult: await publishToWordPress(publishPayload),\n  };\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Formats the article and sends it to WordPress. Returns both the payload and the result so the caller can inspect exactly what was sent and what came back.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. The Edges \u2014 Control Flow with Conditional Routing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Edges define the graph topology. Fixed edges (from <code>START<\/code>, between sequential nodes) and a conditional edge from the review node that branches based on the <code>approved<\/code> flag:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function routeAfterReview(state: WorkflowState): string {\n  if (!state.review) return END;\n  if (state.review.approved) return \"publisher_step\";\n  if (state.reviewAttempts &gt;= REVIEW_MAX_ATTEMPTS) return END;\n  return \"writer_step\";\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The routing function reads the state and returns the name of the next node. The graph wiring:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const workflow = new StateGraph(WorkflowAnnotation)\n  .addNode(\"research_step\", researchNode)\n  .addNode(\"writer_step\", writerNode)\n  .addNode(\"review_step\", reviewNode)\n  .addNode(\"publisher_step\", publisherNode)\n  .addEdge(START, \"research_step\")\n  .addEdge(\"research_step\", \"writer_step\")\n  .addEdge(\"writer_step\", \"review_step\")\n  .addConditionalEdges(\"review_step\", routeAfterReview, {\n    publisher: \"publisher_step\",\n    writer: \"writer_step\",\n    &#91;END]: END,\n  })\n  .addEdge(\"publisher_step\", END);\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the complete graph definition. The resulting flow:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>START \u2192 research \u2192 writer \u2192 review \u2192 (approved? publisher : writer or END)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The beauty of this representation is that it is <strong>visualisable<\/strong>. You can draw this graph, inspect it at runtime, and extend it by adding nodes and edges \u2014 without touching the existing node code.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. Running the Graph<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The compiled graph exposes a single <code>invoke()<\/code> method that accepts the initial state and returns the final state after all nodes have run:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export const app = workflow.compile();\n\nexport async function runWorkflowGraph(topic: string): Promise&lt;WorkflowState&gt; {\n  const result = await app.invoke({ topic, reviewAttempts: 0 });\n  return result as WorkflowState;\n}\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>npm run build\nnode dist\/demo.js \"AI sales agents for SMBs\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The demo prints every field of the final state:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Starting LangGraph workflow...\n\nResearch: { \"keyword\": \"...\", \"angle\": \"...\", \"audience\": \"...\", \"headings\": &#91;\"...\"] }\nDraft: { \"title\": \"...\", \"summary\": \"...\", \"slug\": \"...\" }\nReview: { \"approved\": true, \"score\": 92, \"issues\": &#91;] }\nPublish payload: { \"title\": \"...\", \"status\": \"draft\" }\nPublish result: { \"success\": true, \"postId\": 1583,\n  \"url\": \"https:\/\/your-site.com\/?p=1583\" }\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">7. What LangGraph Adds \u2014 And What It Costs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">After four examples without frameworks, this one adds a real one. The difference is instructive.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What it adds<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Explicit graph structure<\/strong> \u2014 the nodes and edges are declared as a graph, not hidden inside async function calls. You can see the entire workflow in one block of code.<\/li>\n\n\n\n<li><strong>Conditional routing<\/strong> \u2014 the review \u2192 writer revision loop is a conditional edge, not a <code>while<\/code> loop in the orchestrator. The routing function is a pure function of state, which makes it testable in isolation.<\/li>\n\n\n\n<li><strong>State management<\/strong> \u2014 the graph engine merges partial state updates from each node. You never accidentally overwrite a field from a previous node because the merge is automatic.<\/li>\n\n\n\n<li><strong>Compile-time validation<\/strong> \u2014 <code>workflow.compile()<\/code> checks that all node names referenced in edges actually exist, that there are no cycles where there should not be, and that the state annotation keys are consistent.<\/li>\n\n\n\n<li><strong>Inspectability<\/strong> \u2014 LangGraph can log every state transition, every node execution time, and the final state. Debugging a failed run means replaying the graph.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">What it costs<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Two dependencies<\/strong> \u2014 <code>@langchain\/langgraph<\/code> and <code>@langchain\/core<\/code>. The core example in this article adds ~5MB to <code>node_modules<\/code>.<\/li>\n\n\n\n<li><strong>API learning curve<\/strong> \u2014 <code>Annotation.Root<\/code>, <code>StateGraph<\/code>, <code>addConditionalEdges<\/code>, channel naming rules (a node cannot share a name with a state attribute). These are not complex, but they are something to learn.<\/li>\n\n\n\n<li><strong>Indirection<\/strong> \u2014 the actual work still happens in plain async functions. The graph is a layer on top that routes state between them. For simple linear pipelines, the plain function chain is less code and easier to follow.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">When to use it<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The graph model shines when your workflow has:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Multiple branching paths (approve vs. reject, publish vs. archive)<\/li>\n\n\n\n<li>Parallel execution (run three research agents at once)<\/li>\n\n\n\n<li>Nested or looping subgraphs (a review loop inside a publishing workflow)<\/li>\n\n\n\n<li>Complex state merging requirements<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For a simple linear handoff \u2014 the kind most small content teams need \u2014 the plain async function chain from the TShandoff example is simpler, lighter, and just as reliable. The graph adds value when the control flow itself becomes complex enough to need its own representation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">8. Putting It All Together<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here is the checklist to build your own LangGraph workflow:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Define your state type<\/strong> \u2014 <code>WorkflowState<\/code> with all the fields each node will read and write. Use <code>Annotation.Root<\/code> to register them as graph channels.<\/li>\n\n\n\n<li><strong>Write node functions<\/strong> \u2014 each node is <code>(state) =&gt; Partial&lt;state&gt;<\/code>. It reads what it needs and returns only the fields it changes. The graph engine merges.<\/li>\n\n\n\n<li><strong>Write the routing function<\/strong> \u2014 a pure function that reads the state and returns the next node name. This is the brain of the conditional logic.<\/li>\n\n\n\n<li><strong>Wire the graph<\/strong> \u2014 <code>addNode()<\/code>, <code>addEdge()<\/code>, <code>addConditionalEdges()<\/code>. The graph is a declarative description of the workflow.<\/li>\n\n\n\n<li><strong>Compile and invoke<\/strong> \u2014 <code>workflow.compile()<\/code> validates the graph, then <code>app.invoke(initialState)<\/code> runs it.<\/li>\n\n\n\n<li><strong>Inspect the result<\/strong> \u2014 the final state contains every intermediate value. Log it, save it, debug it.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>That is the entire pattern.<\/strong> The graph is not doing anything the plain async functions could not do \u2014 it is doing the same thing, but with explicit structure, state management and compile-time validation. Whether that is worth the dependency cost depends entirely on the complexity of your workflow.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Going Further<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Add <code>Promise.all<\/code> parallelism<\/strong> \u2014 LangGraph has built-in parallel node execution. Run three research <a href=\"https:\/\/www.juust.org\/index.php\/tag\/agent\/\" target=\"_blank\" data-type=\"link\" data-id=\"https:\/\/www.juust.org\/index.php\/tag\/agent\/\" rel=\"noreferrer noopener\">agents<\/a> simultaneously for different keywords.<\/li>\n\n\n\n<li><strong>Human-in-the-loop<\/strong> \u2014 LangGraph supports <code>interrupt()<\/code> to pause the graph and wait for human input before continuing.<\/li>\n\n\n\n<li><strong>Subgraphs<\/strong> \u2014 wrap the review loop as a subgraph and reuse it across different content types.<\/li>\n\n\n\n<li><strong>Persist state<\/strong> \u2014 LangGraph has checkpointers that save state to a database, allowing the workflow to survive restarts.<\/li>\n\n\n\n<li><strong>Mix with the other examples<\/strong> \u2014 the <code>MiniAgent<\/code> class from the earlier TypeScript examples uses tools (calculator, web search, etc.). You can drop that class into any LangGraph node as a node function.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The full source is at https:\/\/github.com\/juustesout\/typescript-agent-example-langgraph in the <code>TSLangGraph<\/code> folder. Open <code>graph.ts<\/code> \u2014 it is one file, four nodes, five edges, and the exact same agent logic you already understand from the previous four examples.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Build an LangGraph Orchestrator pattern in TypeScript (with repo)<\/p>\n","protected":false},"author":5796,"featured_media":21686,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_sitemap_exclude":false,"_sitemap_priority":"","_sitemap_frequency":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[4],"tags":[],"class_list":["post-21685","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-juust"],"_links":{"self":[{"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/posts\/21685","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/users\/5796"}],"replies":[{"embeddable":true,"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/comments?post=21685"}],"version-history":[{"count":6,"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/posts\/21685\/revisions"}],"predecessor-version":[{"id":21722,"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/posts\/21685\/revisions\/21722"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/media\/21686"}],"wp:attachment":[{"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/media?parent=21685"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/categories?post=21685"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.juust.org\/index.php\/wp-json\/wp\/v2\/tags?post=21685"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}