Manage FAQs

Write answers, promote reviewed proposals, and publish FAQ versions for retrieval.

Publish a FAQ to give agents an approved answer to a recurring question. Write it directly or promote a reviewed proposal.

Before you begin

Use an existing playbook and an API key with write permission to create and maintain entries. Publishing and withdrawing require publish. See access permissions.

Start with the quickstart's client setup, then append this page's snippets in order. Run each write once.

The CLI uses flat FAQ JSON files and faqs push to reconcile content by entry key. A changed file appends a version; an unchanged file does not. Publication remains a separate command.

Write an answer

Write a question in the language a user would use. Include conditions that distinguish it from similar questions, such as the product or region. Make the answer self-contained: the agent receives it without the source conversation or document.

The sample FAQ explains where to find refund status. Use a skill when the agent must interpret the customer's reported stage, choose a template, and draft a reply.

SDK and HTTP requests nest content fields; CLI FAQ files use flat fields:

Request field CLI file field Use it for
content.payload.faq.question question The recurring question.
content.payload.faq.answer answer The approved answer.
content.intents intents Optional task phrases returned with discovery previews.
content.attributes attributes Exact-match filters, such as audience, locale, or product.
content.metadata metadata Application context, such as a source-document reference.

Semantic matching uses the question, not the answer or intents. Use attributes to filter the audience. Metadata does not control publication.

For example, prefer “How do I check the status of an existing refund?” to “Refund help.” Keep “How do I request a refund?” separate: it needs a different answer.

Create a draft FAQ

Submit the complete content with create_faq. The sample assigns refund-status as its entry key. Keys are immutable and unique within a playbook; choose another key if that one is already in use. Save the returned entry identifier and version number.

The new FAQ is unpublished. Review it before publishing; search and discovery do not return it yet.

faq_content = {
    "intents": ["Check a refund", "Find an existing refund"],
    "attributes": {"locale": "en-US", "topic": "refund"},
    "payload": {
        "faq": {
            "question": "How do I check the status of a refund?",
            "answer": (
                "Open the order in your order history and select Refund status. "
                "If no refund is listed, contact support with your order number."
            ),
        }
    },
}

draft = playbook.client.create_faq(content=faq_content, entry_key="refund-status")
faq_id = draft.entry.entry_id
version = draft.version.version
print(faq_id, version, draft.entry.publication)
cat > faq.json <<'JSON'
{
  "entryKey": "refund-status",
  "question": "How do I check the status of a refund?",
  "answer": "Open the order in your order history and select Refund status. If no refund is listed, contact support with your order number.",
  "intents": ["Check a refund", "Find an existing refund"],
  "attributes": {"locale": "en-US", "topic": "refund"}
}
JSON
iris agent-playbooks faqs validate faq.json
iris --json agent-playbooks faqs push faq.json > faq-draft.json
FAQ_ID=$(jq -er '.results[0].entryId' faq-draft.json)
VERSION=$(jq -er '.results[0].version' faq-draft.json)
jq . faq-draft.json
cat > faq.json <<'JSON'
{
  "entryKey": "refund-status",
  "content": {
    "intents": ["Check a refund", "Find an existing refund"],
    "attributes": {"locale": "en-US", "topic": "refund"},
    "payload": {"faq": {
      "question": "How do I check the status of a refund?",
      "answer": "Open the order in your order history and select Refund status. If no refund is listed, contact support with your order number."
    }}
  }
}
JSON
curl --fail-with-body --silent --show-error \
  -X POST "$PLAYBOOK_API/faqs" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
  -H 'Content-Type: application/json' --data-binary @faq.json > draft.json
FAQ_ID=$(jq -er '.entry.entryId' draft.json)
VERSION=$(jq -er '.version.version' draft.json)
jq '{entryId: .entry.entryId, version: .version.version, publication: .entry.publication}' draft.json

Promote a reviewed proposal

If an answer comes from the proposal inbox, review the candidate and its evidence first. Accept the proposal with the revision you reviewed.

Acceptance returns an entry and version for one unpublished FAQ. Save those values and continue with publication. Promotion does not make the answer available to agents, and it does not update an existing FAQ.

Publish a version

  1. Retrieve the exact version you intend to publish.
  2. Review its answer, intents, and audience attributes.
  3. Publish that version with publish_faq.
  4. Test retrieval with a reader key and a representative question.

Test a paraphrase such as “Where can I see my refund?” and a nearby question the answer does not cover, such as “How do I request a refund?” Check the returned entry and audience, then use the retrieval guide to tune matching.

stored = playbook.client.get_faq_version(faq_id=faq_id, version=version)
print(stored.content)
iris --json agent-playbooks faqs show "$FAQ_ID" --version "$VERSION" | jq .content
curl --fail-with-body --silent --show-error \
  "$PLAYBOOK_API/faqs/$FAQ_ID/versions/$VERSION" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" | jq .content

Publish the version you reviewed.

playbook.client.publish_faq(faq_id=faq_id, version=version)
iris agent-playbooks faqs publish "$FAQ_ID" --version "$VERSION"
curl --fail-with-body --silent --show-error \
  -X PUT "$PLAYBOOK_API/faqs/$FAQ_ID/publication" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --argjson version "$VERSION" '{version: $version}')" | jq .publication

Update an answer

Append a complete replacement version to the same entry with append_faq_version. Include the intents, attributes, and metadata you want that version to contain.

Coordinate publishers for the same entry. Concurrent publication changes are last-write-wins. Publishing an already published version is a no-op.

from copy import deepcopy

updated_content = deepcopy(faq_content)
updated_content["payload"]["faq"]["answer"] += (
    " Support can check the current stage but cannot guarantee a bank posting date."
)
updated = playbook.client.append_faq_version(faq_id=faq_id, content=updated_content)
print("Review this version:", updated.version.version, updated.version.content)
jq '.answer += " Support can check the current stage but cannot guarantee a bank posting date."' \
  faq.json > faq-update.json
iris --json agent-playbooks faqs push faq-update.json > faq-updated.json
UPDATED_VERSION=$(jq -er '.results[0].version' faq-updated.json)
iris --json agent-playbooks faqs show "$FAQ_ID" --version "$UPDATED_VERSION" | jq .content
jq 'del(.entryKey) | .content.payload.faq.answer += " Support can check the current stage but cannot guarantee a bank posting date."' \
  faq.json > faq-update.json
curl --fail-with-body --silent --show-error \
  -X POST "$PLAYBOOK_API/faqs/$FAQ_ID/versions" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
  -H 'Content-Type: application/json' --data-binary @faq-update.json > updated.json
jq .version updated.json

The identifier stays the same, and search still serves the first publication. Review and publish the new version, then search again.

playbook.client.publish_faq(faq_id=faq_id, version=updated.version.version)
iris agent-playbooks faqs publish "$FAQ_ID" --version "$UPDATED_VERSION"
curl --fail-with-body --silent --show-error \
  -X PUT "$PLAYBOOK_API/faqs/$FAQ_ID/publication" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq '{version: .version.version}' updated.json)" | jq .publication

Republish the first version to roll back.

playbook.client.publish_faq(faq_id=faq_id, version=version)
iris agent-playbooks faqs publish "$FAQ_ID" --version "$VERSION"
curl --fail-with-body --silent --show-error \
  -X PUT "$PLAYBOOK_API/faqs/$FAQ_ID/publication" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data "$(jq -n --argjson version "$VERSION" '{version: $version}')" | jq .publication

List entries and inspect history

Use list_faqs to list entries, including unpublished ones, and get_faq to read a summary. Both require read. Summaries show the latest version number and any publication, without draft content.

Follow the pagination cursor until it is absent, even after an empty page. Keep filters and sort settings unchanged. Responses return items and nextCursor and don't include a total count. Lists can change while you page through them.

CLI lists and version history follow all cursors automatically and return an array. Use --page for one page with nextCursor, and --cursor to continue it. The CLI's version output currently omits contentDigest; use an SDK or cURL example when you need that field.

cursor = None
while True:
    page = playbook.client.list_faqs(sort_by="updatedAt", sort_order="DESC", limit=50, cursor=cursor)
    for entry in page.items:
        print(entry.entry_id, entry.latest_version, entry.publication)
    cursor = page.next_cursor
    if cursor is None:
        break
iris --json agent-playbooks faqs list --sort-by updatedAt --sort-order desc --page-size 50 | jq .
CURSOR=''
while true; do
  PAGE_ARGS=(--data-urlencode sortBy=updatedAt --data-urlencode sortOrder=DESC --data-urlencode limit=50)
  if [[ -n "$CURSOR" ]]; then PAGE_ARGS+=(--data-urlencode "cursor=$CURSOR"); fi
  curl --fail-with-body --silent --show-error --get "$PLAYBOOK_API/faqs" \
    -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" "${PAGE_ARGS[@]}" > faq-page.json
  jq '.items[] | {entryId, latestVersion, publication}' faq-page.json
  CURSOR=$(jq -r '.nextCursor // empty' faq-page.json)
  [[ -n "$CURSOR" ]] || break
done

For inventory counts, call get_playbook_stats with a reader key. Each count has an available flag: unavailable is different from zero. Counts are observations, not a fixed snapshot of the lists.

stats = playbook.client.get_playbook_stats()
print("Observed at:", stats.observed_at)
for label, count in (("FAQs", stats.faqs.total), ("Skills", stats.skills.total)):
    print(label, count.value if count.available else "unavailable")
curl --fail-with-body --silent --show-error \
  "$PLAYBOOK_API/stats" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
  | jq '{observedAt, faqs: (if .faqs.total.available then .faqs.total.value else "unavailable" end), skills: (if .skills.total.available then .skills.total.value else "unavailable" end)}'

To select only published or unpublished entries, set published to true or false. Omit it to include both.

Listing full version history requires write or publish. Versions arrive newest first. This example uses the FAQ created in this guide:

cursor = None
while True:
    page = playbook.client.list_faq_versions(faq_id=faq_id, limit=50, cursor=cursor)
    for item in page.items:
        print(item.version, item.content_digest, item.content)
    cursor = page.next_cursor
    if cursor is None:
        break
iris --json agent-playbooks faqs versions "$FAQ_ID" --page-size 50 | jq .
CURSOR=''
while true; do
  PAGE_ARGS=(--data-urlencode limit=50)
  if [[ -n "$CURSOR" ]]; then PAGE_ARGS+=(--data-urlencode "cursor=$CURSOR"); fi
  curl --fail-with-body --silent --show-error --get "$PLAYBOOK_API/faqs/$FAQ_ID/versions" \
    -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" "${PAGE_ARGS[@]}" > faq-versions.json
  jq '.items[] | {version, contentDigest, content}' faq-versions.json
  CURSOR=$(jq -r '.nextCursor // empty' faq-versions.json)
  [[ -n "$CURSOR" ]] || break
done

Look up entry state

Use lookup_entries with write or publish permission to inspect known FAQs or skills. Supply 1–100 references, each containing either an entry identifier or an entry key. Results preserve request order and report missing entries explicitly. The batch is not a fixed snapshot.

The published CLI has no batch lookup command. Use one of the SDK or cURL tabs for this operation.

Each found entry includes latest, optional publication, and versionCount. The response also reports the playbook's maxVersions limit. The two version summaries include contentDigest: a server-generated hash of normalized authored content. Compare their digests to check whether the latest and published content match. It is not a hash of the uploaded ZIP or the raw request, and it is not an idempotency key.

from redis_agent_playbook.models import Found

lookup = playbook.client.lookup_entries(
    references=[
        {"entry_id": faq_id},
        {"entry_key": "refund-status"},
    ]
)
print("Version limit:", lookup.max_versions)
for result in lookup.items:
    if isinstance(result, Found):
        state = result.found.entry
        print(state.entry_id, state.version_count, state.latest, state.publication)
    else:
        print("Missing:", result.missing.reference)
jq -n --arg id "$FAQ_ID" '{references: [{entryId: $id}, {entryKey: "refund-status"}]}' \
  | curl --fail-with-body --silent --show-error \
    -X POST "$PLAYBOOK_API/entries/lookup" \
    -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" \
    -H 'Content-Type: application/json' --data-binary @- \
  | jq '{maxVersions, items}'

The example finds the same FAQ by identifier and by key. Either identifies the entry across versions, not a particular publication. Before retrying an uncertain write, inspect its state and relevant version. Appending identical content can still create another version.

Withdraw or delete an FAQ

Use withdraw_faq to stop serving the answer while retaining the entry and its versions. Use delete_faq to remove the entry and its history permanently.

Set PLAYBOOK_FAQ_ID to the printed entry identifier before running a withdrawal or deletion example with the same connection values.

playbook.client.withdraw_faq(faq_id=faq_id)
iris agent-playbooks faqs withdraw "$PLAYBOOK_FAQ_ID"
curl --fail-with-body --silent --show-error \
  -X DELETE "$PLAYBOOK_API/faqs/$FAQ_ID/publication" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY" | jq .

When you no longer need the sample history, delete the entry.

playbook.client.delete_faq(faq_id=faq_id)
iris agent-playbooks faqs delete "$PLAYBOOK_FAQ_ID" --yes
curl --fail-with-body --silent --show-error \
  -X DELETE "$PLAYBOOK_API/faqs/$FAQ_ID" \
  -H "Authorization: Bearer $IRIS_AGENT_PLAYBOOKS_API_KEY"
RATE THIS PAGE
Back to top ↑