{"id":164,"date":"2026-09-20T02:30:08","date_gmt":"2026-09-19T18:30:08","guid":{"rendered":"https:\/\/wp.qoraapi.com\/prompt-management-versioning\/"},"modified":"2026-09-20T02:51:12","modified_gmt":"2026-09-19T18:51:12","slug":"prompt-management-versioning","status":"publish","type":"post","link":"https:\/\/qoraapi.com\/blog\/prompt-management-versioning\/","title":{"rendered":"Prompt Management and Versioning in Production"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Prompt management is the practice of storing every prompt as a versioned artifact \u2014 a template with typed variables, an immutable version id, and a pinned reference in code \u2014 so a wording change ships through review, CI eval gates, and a canary rollout instead of a silent hotfix. Versioning turns prompt edits into a deployable, reversible operation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Most teams already have a model gateway, a retry policy, and a latency dashboard. What they lack is a single answer to &#8220;which prompt produced this response, and what exactly did it say?&#8221; This guide builds that answer: artifact storage, immutable versions, eval gates in CI, per-version metrics, canary rollout with a kill switch, and a working prompt registry you can copy.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why prompts are production code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A prompt is an untyped program running on a non-deterministic interpreter. It changes user-visible behavior and it needs review \u2014 yet it usually lives as a string literal buried in a handler, or a row somebody edited in a dashboard at 6pm.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What makes prompts dangerous is the shape of their failures. Code changes fail loudly: a bad deploy throws, tests go red, the health check flips. Prompt changes fail silently. The response still parses as JSON, still reads fluently, and is still wrong \u2014 or correct but twice as long and twice as expensive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Three properties make a prompt a first-class deployable:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>It changes behavior.<\/strong> Adding &#8220;be concise&#8221; to a support prompt can cut output tokens by a third and simultaneously drop the detail users actually needed. Same code, different product.<\/li>\n<li><strong>It is coupled to the model.<\/strong> A prompt tuned against one model snapshot is not portable. Swapping models without re-running evals is a behavior change nobody reviewed.<\/li>\n<li><strong>It regresses without raising.<\/strong> Nothing throws when eval pass rate falls from 92% to 78%. Only a gate catches that, and only if the gate runs before production traffic does.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The practical consequence: the unit you deploy is never &#8220;the prompt.&#8221; It is the template, its few-shot examples, its tool and output schemas, the model id, and the sampling parameters \u2014 together. Change any one of them and you have a new version.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Treating prompts as artifacts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An artifact has an identity, a version, and one source of truth. Prompts fail that test when they are scattered as f-strings across handlers: the same logical prompt drifts into five near-duplicates, each slightly different, and nobody knows which is canonical.<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table class=\"has-fixed-layout\"><thead><tr><th>Storage model<\/th><th>Where it wins<\/th><th>What it costs you<\/th><\/tr><\/thead><tbody><tr><td>Files in Git<\/td><td>Every change gets a diff, a reviewer, and a CI gate; prompts version atomically with the code that calls them<\/td><td>Any edit is a deploy; non-engineers cannot change wording<\/td><\/tr><tr><td>Prompt registry \/ DB<\/td><td>Runtime updates without a deploy; per-version routing and instant rollback<\/td><td>Rows are editable out of band, so the review trail is easy to lose<\/td><\/tr><tr><td>Hybrid (recommended)<\/td><td>Git is the source of truth; CI syncs merged files into the registry; the app reads the registry<\/td><td>You must build and monitor the sync step<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Whatever you pick, the prompt must be a template \u2014 not an f-string. Use neutral <code>{{variable}}<\/code> placeholders, declare the variable set explicitly, and render with strict validation so a missing value fails at the boundary instead of rendering the string &#8220;None&#8221; into a customer-facing reply:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from dataclasses import dataclass\n\n@dataclass(frozen=True)\nclass Template:\n    id: str\n    version: int\n    body: str                  # neutral {{var}} syntax, provider-agnostic\n    variables: tuple           # the contract: exactly these, nothing else\n\n    def render(self, **values):\n        missing = set(self.variables) - values.keys()\n        extra   = set(values) - set(self.variables)\n        if missing or extra:\n            # Fail at the boundary. A prompt that renders \"None\" into a\n            # customer-facing string is worse than a 500.\n            raise ValueError(f\"{self.id}@{self.version} missing={missing} extra={extra}\")\n        out = self.body\n        for key, value in values.items():\n            out = out.replace(\"{{\" + key + \"}}\", str(value))\n        return out\n\nREPLY = Template(\n    id=\"support.reply\",\n    version=14,\n    body=\"You are a {{plan}} support agent.\\n\\nQuestion: {{question}}\\n\"\n         \"Answer in at most {{max_sentences}} sentences.\",\n    variables=(\"plan\", \"question\", \"max_sentences\"),\n)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Strict rendering is not pedantry. Missing variables are the most common silent prompt bug, and unvalidated interpolation is an injection surface: a user string containing your placeholder syntax can reshape the prompt before the model sees it. Validate the variable set first, then interpolate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the placeholder syntax neutral rather than provider-specific: one artifact can then render into different request shapes without rewriting the prompt, which matters as soon as you route across providers. Template design is a separate discipline \u2014 our guide to <a href=\"https:\/\/qoraapi.com\/blog\/ai-prompt-engineering\/\">prompt engineering<\/a> covers wording, structure, and few-shot selection, while this article covers the machinery that keeps those choices safe to change.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Versioning and rollback<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Versioning is nearly free if you follow three rules, and worthless if you break the second one.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Stable id, immutable version.<\/strong> <code>support.reply<\/code> is the identity; <code>14<\/code> is the version. Publishing never edits an existing version \u2014 it creates the next one. Immutability is what turns rollback into a pointer move instead of a re-edit under pressure.<\/li>\n<li><strong>Pin the version in code.<\/strong> Code asks for <code>(\"support.reply\", 14)<\/code>, never for &#8220;latest&#8221;. A floating pointer means two requests a minute apart can run different prompts, and no incident is ever reproducible.<\/li>\n<li><strong>Version the whole request contract.<\/strong> Template plus few-shot examples plus tool schemas plus output schema plus model plus <code>temperature<\/code> and <code>max_tokens<\/code>. Teams that version only the system string get burned the day someone edits a single few-shot example.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Store a content digest next to the integer version: a hash of the normalized template and its variable schema. The digest catches out-of-band edits \u2014 a row changed directly in the database \u2014 and proves that the bytes that ran in production are byte-identical to the bytes in Git. The rule is simple: if the digest changes, the version number must change.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Rollback then costs one line. With a registry that holds channel pointers, <code>promote(\"support.reply\", 13)<\/code> moves production back to the previous version instantly \u2014 no deploy, no revert commit, no waiting on CI. That is the property worth optimizing: time from &#8220;we are wrong&#8221; to &#8220;we are on the old prompt&#8221; should be measured in seconds, not release cycles.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing prompts in CI<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prompts need the same two-tier gate as code: cheap deterministic checks on every commit, and a scored eval run whenever a prompt actually changes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tier 1 \u2014 deterministic assertions (every commit, seconds).<\/strong> These need no model calls and catch most breakage: the template renders with exactly its declared variables, the rendered length stays inside budget, required markers are present, banned strings are absent, and recorded responses still validate against the output schema. Put a token-budget assertion here too \u2014 a prompt edit that doubles the system prompt should fail in CI, not on next month&#8217;s bill.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Tier 2 \u2014 scored eval set (pull requests that touch prompts).<\/strong> Keep a labeled set per prompt: 30 cases to start, 100\u2013200 as the feature matures, mixing happy paths, edge cases, and \u2014 most valuable of all \u2014 one case per production incident that prompt has ever caused. Each new bug becomes a permanent case, so the same regression cannot ship twice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The gate needs a threshold, and the threshold needs a must-pass subset:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Must-pass cases: 100%.<\/strong> Safety, PII, schema-critical, and previously broken cases. A single failure blocks the merge; no averaging allowed.<\/li>\n<li><strong>Aggregate score: no regression beyond a margin.<\/strong> Fail the pull request if the pass rate drops more than a few points, or if mean quality falls outside the last released version&#8217;s confidence interval.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Two disciplines make those numbers trustworthy. First, freeze the eval set and the judge model while you change a prompt \u2014 if you swap the grader and the prompt in the same commit, you cannot attribute the delta to either one. Pin an eval-set version alongside the prompt version. Second, respect noise: with 50 cases, a two-point move is indistinguishable from sampling variance, so either grow the set or set the gate where the difference is real rather than cosmetic. The methodology in our guide to <a href=\"https:\/\/qoraapi.com\/blog\/evaluate-benchmark-ai-models\/\">evaluating AI models<\/a> covers judge design, calibration, and why a fixed judge beats a rotating one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Snapshot testing is the lightweight version of the same idea: record each version&#8217;s output and score, then diff on the next change. The diff is not proof of correctness \u2014 it forces every behavior change into review instead of into production.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A\/B testing and per-version metrics<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Offline evals tell you a version is not worse on your test set. Only production tells you whether it is better for real traffic. Run both versions behind one endpoint, bucket deterministically, and compute the same table for each version.<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table class=\"has-fixed-layout\"><thead><tr><th>Metric<\/th><th>Why it decides the rollout<\/th><th>How to compute it<\/th><\/tr><\/thead><tbody><tr><td>Task pass rate<\/td><td>The only quality number that matters<\/td><td>Automated checks plus sampled judge or human review on live traffic<\/td><\/tr><tr><td>Cost per successful task<\/td><td>A cheap prompt that fails is the expensive one<\/td><td>(tokens x relative price) \/ successful completions<\/td><\/tr><tr><td>Latency p50 \/ p95<\/td><td>Prompt length drives time-to-first-token and total time<\/td><td>Per-version trace timings \u2014 never averages alone<\/td><\/tr><tr><td>Format-compliance rate<\/td><td>Broken JSON is a product outage, not a quality dip<\/td><td>Share of responses passing schema validation on first try<\/td><\/tr><tr><td>Refusal \/ error rate<\/td><td>Rewording can trip safety behavior or provider filters<\/td><td>Refusals and 4xx\/5xx counted per version<\/td><\/tr><tr><td>Retry \/ escalation rate<\/td><td>Proxy for quality loss users notice before you do<\/td><td>Retries, human handoffs, or fallback-model usage per version<\/td><\/tr><tr><td>Tokens per request<\/td><td>Directly sets unit cost and latency<\/td><td>Input plus output tokens, segmented by version<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Three rules turn that table into a decision. <strong>Bucket by a stable key<\/strong> \u2014 hash the user or session id \u2014 so one person never sees two prompt versions in a session; per-request random assignment gives you inconsistent UX and a confounded experiment. <strong>Compare cost per successful task, not cost per call<\/strong>: a version that is 30% cheaper per call but fails 20% more often is the more expensive one once you price the failures. And <strong>check guardrails before quality<\/strong>: if the candidate blows the p95 latency budget or the format-compliance floor, stop, however good the average answer looks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because a unified gateway such as <a href=\"https:\/\/qoraapi.com\/\" target=\"_blank\" rel=\"noopener\">qoraapi.com<\/a> exposes many models behind one OpenAI-compatible endpoint, an A\/B test can vary the model inside the prompt version too \u2014 same registry entry, different <code>model<\/code> string \u2014 which is how you discover that a cheaper model clears the bar for one prompt and quietly fails another.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Deploying prompts safely<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ship prompt versions the way you ship code: gradually, with an automatic stop condition.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Canary by percentage, not by environment.<\/strong> Route 1% of traffic to the new version, then 5%, 25%, 100%. Gate each step on the guardrails above \u2014 error rate, format compliance, p95 latency, escalation rate \u2014 and require the step to hold for a full traffic cycle before widening. Deterministic bucketing keeps the same users in the canary as it grows.<\/li>\n<li><strong>Ship a kill switch.<\/strong> One flag that pins the prompt back to the last good version, readable at runtime without a deploy. Test it before you need it: an untested kill switch is a hope, not a control.<\/li>\n<li><strong>Make the pin the only production input.<\/strong> If any code path can still read &#8220;latest,&#8221; your canary and rollback are advisory. Grep for it in CI and fail the build.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Here is a minimal registry that implements immutability, channel pinning, canary routing, and one-line rollback \u2014 no dependencies, about sixty lines:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import hashlib\nfrom dataclasses import dataclass\n\n@dataclass(frozen=True)\nclass PromptVersion:\n    id: str\n    version: int\n    template: str      # {{var}} placeholders\n    variables: tuple   # required names, enforced at render\n    model: str\n    params: dict       # temperature, max_tokens, ...\n    digest: str        # content hash: proves which bytes ran\n\nclass PromptRegistry:\n    \"\"\"Runtime view of prompts. Git is the source of truth; CI syncs into this.\"\"\"\n\n    def __init__(self):\n        self._versions = {}   # (id, version) -> PromptVersion\n        self._channel = {}    # id -> pinned version for production\n        self._canary = {}     # id -> (candidate version, percent)\n\n    def add(self, pv):\n        key = (pv.id, pv.version)\n        if key in self._versions and self._versions[key].digest != pv.digest:\n            raise ValueError(f\"{key} already published with a different digest\")\n        self._versions[key] = pv\n\n    def promote(self, prompt_id, version):\n        \"\"\"Rollback is this one line: move the pointer. No deploy required.\"\"\"\n        if (prompt_id, version) not in self._versions:\n            raise KeyError(f\"unknown version {prompt_id}@{version}\")\n        self._channel[prompt_id] = version\n\n    def canary(self, prompt_id, version, percent):\n        self._canary[prompt_id] = (version, percent)\n\n    def resolve(self, prompt_id, routing_key):\n        stable = self._channel[prompt_id]\n        candidate = self._canary.get(prompt_id)\n        if candidate:\n            version, percent = candidate\n            bucket = int(hashlib.sha256(\n                f\"{prompt_id}:{routing_key}\".encode()).hexdigest(), 16) % 100\n            if bucket &lt; percent:\n                return self._versions[(prompt_id, version)]\n        return self._versions[(prompt_id, stable)]\n\n    def render(self, pv, **values):\n        missing = set(pv.variables) - values.keys()\n        if missing:\n            raise ValueError(f\"{pv.id}@{pv.version} missing={missing}\")\n        out = pv.template\n        for key, value in values.items():\n            out = out.replace(\"{{\" + key + \"}}\", str(value))\n        return out\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Call it once per request, and log the resolved version alongside the response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pv = registry.resolve(\"support.reply\", routing_key=user_id)\nprompt = registry.render(pv, plan=user.plan, question=question, max_sentences=4)\n\nresponse = client.chat.completions.create(\n    model=pv.model,\n    messages=[{\"role\": \"system\", \"content\": prompt}],\n    **pv.params,\n)\n\nlog.info(\"llm_call\", extra={\n    \"prompt_id\": pv.id,\n    \"prompt_version\": pv.version,\n    \"prompt_digest\": pv.digest,\n    \"model\": pv.model,\n    \"latency_ms\": elapsed_ms,\n    \"out_tokens\": response.usage.completion_tokens,\n})\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Observability: log the prompt version with every call<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you log only the model and the token count, every quality incident becomes an archaeology project. Log the prompt identity with the response and the first question answers itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At minimum, every LLM call should carry: <code>trace_id<\/code>, <code>prompt_id<\/code>, <code>prompt_version<\/code>, <code>prompt_digest<\/code>, <code>model<\/code> (plus the model snapshot if the provider exposes one), input and output tokens, latency, and the outcome of any post-check such as schema validation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Log the digest, not just the version number. The digest is what proves the artifact that ran matches Git \u2014 the difference between &#8220;we deployed v15&#8221; and &#8220;we ran the bytes of v15.&#8221; It also catches the one event that breaks every versioning scheme: somebody editing the registry row directly, out of band.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With that in place the incident workflow becomes mechanical. Pull traces for the failing case, read <code>prompt_version<\/code>, group the metrics by version over the last hour, and compare distributions. If the drop is confined to one version, move the pointer back and investigate offline. If both versions degraded at the same moment, the cause is upstream \u2014 a model change, a provider incident, or a data shift \u2014 and rolling back the prompt will not help. <a href=\"https:\/\/qoraapi.com\/blog\/llm-observability\/\">LLM observability<\/a> covers tracing and cost attribution in depth; the prompt version is the join key that makes those traces answerable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Frequently asked questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Should prompts live in Git or a database?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use both, with Git as the source of truth. Author prompts as files so every change gets a diff, a reviewer, and a CI eval gate; then have CI sync the merged file into a registry the application reads at runtime. Git gives you history and review; the registry gives you instant rollback and canary routing without a deploy. A database alone loses the review trail, and Git alone makes every rollback a release.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Is &#8220;latest&#8221; ever acceptable in production?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. A floating pointer means two requests in the same minute can run different prompts, which destroys reproducibility, invalidates your A\/B results, and makes rollback meaningless. Pin an explicit version and change the pin deliberately. &#8220;Latest&#8221; belongs in a local dev loop and nowhere else.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How large should a prompt eval set be?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Start at 30 cases, grow toward 100\u2013200 as incidents accumulate, and make every production bug a permanent case. Composition matters more than size: edge cases and past failures catch more regressions than a large set of easy examples. If your gate keeps tripping on noise, the set is too small \u2014 grow it instead of loosening the threshold.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need a prompt registry for a single-prompt app?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You need versioning immediately and a registry later. Start with prompts as files in the repo, an explicit version constant pinned in code, and one eval gate in CI. Add a registry when you actually need runtime rollback, canary routing, or non-engineers editing prompts. The registry is an operational convenience; the versioning discipline is what prevents incidents.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prompts are production code with one nasty property: they fail silently. Fix that by making them artifacts \u2014 versioned templates with typed variables, immutable versions, an explicit pin in code \u2014 and by putting the same gates around them that you put around any service. An eval set with a must-pass subset in CI, per-version metrics for quality, cost, and latency, a canary that widens only on guardrail checks, and a kill switch you have actually tested. Then log the prompt version and digest on every call, so the first question in every incident has an answer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Start smaller than you think. Move the strings into files, pin a version, add one eval gate. The registry, the canary, and the A\/B harness are all scale-ups from that base \u2014 and none of them work if the base is missing.<\/p>\n\n\n\n\n<h3 class=\"wp-block-heading\">Related reading<\/h3>\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/qoraapi.com\/blog\/ai-prompt-engineering\/\">AI Prompt Engineering for Reliable API Responses<\/a><\/li><li><a href=\"https:\/\/qoraapi.com\/blog\/evaluate-benchmark-ai-models\/\">Evaluating and Benchmarking AI Models Before You Ship<\/a><\/li><li><a href=\"https:\/\/qoraapi.com\/blog\/llm-observability\/\">LLM Observability: Monitoring AI API Usage, Latency and Cost<\/a><\/li><li><a href=\"https:\/\/qoraapi.com\/blog\/prompt-caching-guide\/\">Prompt Caching Explained: How to Cut Costs on Repeated Context<\/a><\/li><\/ul>\n\n","protected":false},"excerpt":{"rendered":"<p>Treat prompts as versioned production artifacts: templates, immutable versions, CI regression gates, canary rollouts, and logging the prompt version with every call.<\/p>\n","protected":false},"author":1,"featured_media":163,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[5,6,9,7],"class_list":["post-164","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-api","tag-ai-api","tag-api-gateway","tag-developer-tools","tag-developers"],"_links":{"self":[{"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/posts\/164","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/comments?post=164"}],"version-history":[{"count":1,"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/posts\/164\/revisions"}],"predecessor-version":[{"id":190,"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/posts\/164\/revisions\/190"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/media\/163"}],"wp:attachment":[{"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/media?parent=164"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/categories?post=164"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/qoraapi.com\/blog\/wp-json\/wp\/v2\/tags?post=164"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}