> ## Documentation Index
> Fetch the complete documentation index at: https://developer.suki.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Wait for Patient Summary before Fetch

> Poll Patient Summary job status until COMPLETED before calling retrieve APIs

export const CbRecipeMeta = ({items = []}) => <div className="cb-recipe-meta">
    {items.map(item => <span className={`cb-recipe-pill cb-recipe-pill--${item.type}${item.tone ? ` cb-recipe-pill--${item.tone}` : ""}`} key={`${item.type}-${item.label}`}>
        {item.type === "time" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <circle cx="12" cy="12" r="10" />
            <polyline points="12 6 12 12 16 14" />
          </svg> : item.type === "level" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <path d="M2 20h.01" />
            <path d="M7 20v-4" />
            <path d="M12 20v-8" />
            <path d="M17 20V8" />
            <path d="M22 20V4" />
          </svg> : item.type === "surface" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <polyline points="16 18 22 12 16 6" />
            <polyline points="8 6 2 12 8 18" />
          </svg> : <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <path d="M12 3v3" />
            <path d="M12 18v3" />
            <path d="M3 12h3" />
            <path d="M18 12h3" />
            <path d="M5.6 5.6l2.1 2.1" />
            <path d="M16.3 16.3l2.1 2.1" />
            <path d="M5.6 18.4l2.1-2.1" />
            <path d="M16.3 7.7l2.1-2.1" />
          </svg>}
        {item.label}
      </span>)}
  </div>;

export const CbRecipePage = ({title, description, meta, recipeId, sidebar = {}, children}) => {
  const {tutorialHref, tutorialLabel = "Open Full Tutorial", apiHref, apiLabel = "View API Reference", authSections = [], glance = [], headers = []} = sidebar;
  return <div className="hp-wrap api-overview-wrap docs-frame-wrap cb-recipe-wrap" data-cb-recipe data-disable-read-time="true">
      <header className="cb-recipe-hero tut-hub-intro">
        <h1 className="sdk-overview-main-title api-overview-hero-page-title">{title}</h1>
        {description ? <div className="wse-prose">{description}</div> : null}
        {meta ? <div className="cb-recipe-meta-slot">{meta}</div> : null}
      </header>

      <div className="cb-recipe-body">
        <div className="cb-recipe-layout">
          <div className="cb-recipe-main prose prose-gray dark:prose-invert">{children}</div>

          <aside className="cb-recipe-sidebar" aria-label="Recipe actions and metadata">
            {recipeId ? <div className="cb-recipe-sidebar-card cb-recipe-sidebar-card--run">
                <div className="cb-run" data-cb-run data-recipe-id={recipeId} data-recipe-title={title}>
                  <button type="button" className="cb-run-trigger" aria-expanded="false">
                    <svg className="cb-run-trigger-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                      <path d="M8 5v14l11-7L8 5z" />
                    </svg>
                    <span>Run This Recipe</span>
                    <svg className="cb-run-trigger-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <polyline points="6 9 12 15 18 9" />
                    </svg>
                  </button>
                </div>
              </div> : null}

            <div className="cb-recipe-sidebar-card cb-recipe-sidebar-card--actions">
              <div className="cb-recipe-sidebar-actions">
                {tutorialHref ? <a className="cb-recipe-action cb-recipe-action--primary" href={tutorialHref}>
                    <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
                      <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
                    </svg>
                    <span>{tutorialLabel}</span>
                  </a> : null}
                {apiHref ? <a className="cb-recipe-action cb-recipe-action--secondary" href={apiHref}>
                    <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <path d="M8 4H6a2 2 0 0 0-2 2v4" />
                      <path d="M8 20H6a2 2 0 0 1-2-2v-4" />
                      <path d="M16 4h2a2 2 0 0 1 2 2v4" />
                      <path d="M16 20h2a2 2 0 0 0 2-2v-4" />
                    </svg>
                    <span>{apiLabel}</span>
                  </a> : null}
              </div>
              <div className="cb-recipe-sidebar-caption">
                Step-by-step tutorial and API reference for this workflow.
              </div>
            </div>

            {authSections.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">Auth</div>
                {authSections.map(section => <div className="cb-recipe-auth-block" key={section.title}>
                    <div className="cb-recipe-auth-title">{section.title}</div>
                    <div className="cb-recipe-sidebar-copy">
                      {section.text}{" "}
                      {section.href ? <a href={section.href}>{section.linkLabel || "Learn more"}</a> : null}
                    </div>
                    {section.scopes?.length ? <div className="cb-recipe-scope-pills cb-recipe-auth-scopes">
                        {section.scopes.map(scope => <span className="cb-recipe-scope-pill" key={scope}>
                            {scope}
                          </span>)}
                      </div> : null}
                  </div>)}
              </div> : null}

            {glance.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">At a glance</div>
                <dl className="cb-recipe-glance">
                  {glance.map(row => <div className="cb-recipe-glance-row" key={row.label}>
                      <dt>{row.label}</dt>
                      <dd>{row.value}</dd>
                    </div>)}
                </dl>
              </div> : null}

            {headers.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">Required headers</div>
                <div className="cb-recipe-auth-title">Partner API</div>
                <div className="cb-recipe-scope-pills">
                  {headers.map(h => <span className="cb-recipe-scope-pill" key={h}>
                      {h}
                    </span>)}
                </div>
              </div> : null}
          </aside>
        </div>
      </div>
    </div>;
};

<CbRecipePage
  recipeId="poll-patient-summary-status-before-retrieve"
  title="Wait for Patient Summary before Fetch"
  description="Patient Summary retrieve APIs need a completed job. Poll generation status until `COMPLETED`, then call retrieve. Treat `FAILED` and `ABORTED` as terminal outcomes."
  meta={
<CbRecipeMeta
  items={[
    { type: "time", label: "5 min" },
    { type: "level", label: "Beginner" },
    { type: "product", label: "Patient Summary" },
    { type: "surface", label: "API", tone: "api" },
  ]}
/>
}
  sidebar={{
apiHref: "/patient-summary-api-reference/summary-jobs/encounter-status",
authSections: [
  {
    title: "Partner API",
    text: "Authenticate every REST call with Partner Token headers on each request. See",
    href: "/documentation/get-started/partner-authentication",
    linkLabel: "Partner authentication",
    scopes: ["sdp_suki_token", "sdp_provider_id"],
  },
],
glance: [
  { label: "Product", value: "Patient Summary" },
  { label: "Surface", value: "Partner API" },
  { label: "Time", value: "~5 min" },
  { label: "Level", value: "Beginner" },
],
}}
>
  ## Problem

  Retrieve APIs return empty or missing summaries right after you trigger generation. The job is still `READY` or `RUNNING`, so retrieve endpoints have nothing to return yet.

  Patient Summary generation is asynchronous. Poll job status until `COMPLETED`, then call retrieve. Treat `FAILED` and `ABORTED` as terminal so your loop exits cleanly.

  ## Architecture

  ```mermaid actions={false} theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
  %%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#FFFBDE','primaryTextColor':'#1C1C1C','primaryBorderColor':'#FFD147','lineColor':'#6F5410','secondaryColor':'#FFFDF5','tertiaryColor':'#FFEB94','fontSize':'13px'}}}%%
  flowchart LR
    A[Generate summary] --> B[Poll status]
    B --> C{Status}
    C -->|COMPLETED| D[Retrieve]
    C -->|FAILED / ABORTED| E[Stop]
    C -->|READY / RUNNING| B
  ```

  ## Prerequisites

  <Check>Triggered generation for the encounter and stored `patient_summary_id`. See [Generate a Patient Summary](/documentation/cookbooks/generate-patient-summary-for-encounter).</Check>

  <Check>Completed [Partner onboarding](/documentation/get-started/partner-onboarding) with `sdp_suki_token` and `sdp_provider_id` headers ready.</Check>

  <Check>The same `fhir_encounter_id` and `fhir_practitioner_id` values you used to generate.</Check>

  ## Solution

  <Columns cols={3}>
    <Card title="Before">
      [Generate a Patient Summary](/documentation/cookbooks/generate-patient-summary-for-encounter)
    </Card>

    <Card title="This Recipe">
      Poll until `COMPLETED`, or stop on `FAILED` / `ABORTED`.
    </Card>

    <Card title="Next">
      Retrieve the summary by FHIR ids (or by `patient_summary_id` if you stored it).
    </Card>
  </Columns>

  Poll [Get patient summary generation status for encounter](/patient-summary-api-reference/summary-jobs/encounter-status) until `COMPLETED`, then retrieve. Treat `FAILED` and `ABORTED` as terminal. You can also poll by `patient_summary_id` with [Get patient summary job status](/patient-summary-api-reference/summary-jobs/job-status).

  <Tabs>
    <Tab title="TypeScript">
      ```typescript theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
      // poll-patient-summary-status-before-retrieve.ts

      const BASE_URL = process.env.SUKI_BASE_URL ?? "https://sdp.suki.ai";
      const SDP_SUKI_TOKEN = process.env.SDP_SUKI_TOKEN!;
      const SDP_PROVIDER_ID = process.env.SDP_PROVIDER_ID ?? "";
      const FHIR_ENCOUNTER_ID = process.env.FHIR_ENCOUNTER_ID ?? "enc-123";
      const FHIR_PRACTITIONER_ID = process.env.FHIR_PRACTITIONER_ID ?? "pract-456";

      function restHeaders(): Record<string, string> {
        const headers: Record<string, string> = { sdp_suki_token: SDP_SUKI_TOKEN };
        if (SDP_PROVIDER_ID) headers.sdp_provider_id = SDP_PROVIDER_ID;
        return headers;
      }

      async function waitUntilCompleted(
        fhirEncounterId: string,
        fhirPractitionerId: string,
        pollMs = 2000,
        maxAttempts = 60
      ): Promise<string> {
        for (let i = 0; i < maxAttempts; i++) {
          const res = await fetch(
            `${BASE_URL}/api/v1/patient-summary/encounter/${fhirEncounterId}/practitioner/${fhirPractitionerId}/status`,
            { headers: restHeaders() }
          );

          if (!res.ok) {
            throw new Error(`Get patient summary status failed: ${res.status}`);
          }

          const { status } = (await res.json()) as { status: string };
          if (status === "COMPLETED") return "COMPLETED";
          if (status === "FAILED" || status === "ABORTED") return status;

          await new Promise((r) => setTimeout(r, pollMs));
        }

        throw new Error("Timed out waiting for patient summary status");
      }

      async function retrievePatientSummary(
        fhirEncounterId: string,
        fhirPractitionerId: string
      ): Promise<unknown> {
        const res = await fetch(
          `${BASE_URL}/api/v1/patient-summary/encounter/${fhirEncounterId}/practitioner/${fhirPractitionerId}`,
          { headers: restHeaders() }
        );
        if (!res.ok) {
          throw new Error(`Retrieve patient summary failed: ${res.status}`);
        }
        return res.json();
      }

      const outcome = await waitUntilCompleted(FHIR_ENCOUNTER_ID, FHIR_PRACTITIONER_ID);
      if (outcome === "COMPLETED") {
        const summary = await retrievePatientSummary(
          FHIR_ENCOUNTER_ID,
          FHIR_PRACTITIONER_ID
        );
        console.log(JSON.stringify(summary, null, 2));
      } else {
        console.error("Terminal status:", outcome);
      }
      ```
    </Tab>

    <Tab title="Python">
      ```python theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
      # poll_patient_summary_status_before_retrieve.py
      # pip install requests

      import json
      import os
      import time

      import requests

      BASE_URL = os.environ.get("SUKI_BASE_URL", "https://sdp.suki.ai")
      SDP_SUKI_TOKEN = os.environ["SDP_SUKI_TOKEN"]
      SDP_PROVIDER_ID = os.environ.get("SDP_PROVIDER_ID", "")
      FHIR_ENCOUNTER_ID = os.environ.get("FHIR_ENCOUNTER_ID", "enc-123")
      FHIR_PRACTITIONER_ID = os.environ.get("FHIR_PRACTITIONER_ID", "pract-456")


      def rest_headers() -> dict[str, str]:
          headers = {"sdp_suki_token": SDP_SUKI_TOKEN}
          if SDP_PROVIDER_ID:
              headers["sdp_provider_id"] = SDP_PROVIDER_ID
          return headers


      def wait_until_completed(
          fhir_encounter_id: str,
          fhir_practitioner_id: str,
          poll_s: float = 2.0,
          max_attempts: int = 60,
      ) -> str:
          for _ in range(max_attempts):
              res = requests.get(
                  f"{BASE_URL}/api/v1/patient-summary/encounter/{fhir_encounter_id}/practitioner/{fhir_practitioner_id}/status",
                  headers=rest_headers(),
                  timeout=30,
              )
              res.raise_for_status()
              status = res.json().get("status")
              if status == "COMPLETED":
                  return "COMPLETED"
              if status in ("FAILED", "ABORTED"):
                  return status
              time.sleep(poll_s)
          raise TimeoutError("Timed out waiting for patient summary status")


      def retrieve_patient_summary(fhir_encounter_id: str, fhir_practitioner_id: str):
          res = requests.get(
              f"{BASE_URL}/api/v1/patient-summary/encounter/{fhir_encounter_id}/practitioner/{fhir_practitioner_id}",
              headers=rest_headers(),
              timeout=30,
          )
          res.raise_for_status()
          return res.json()


      outcome = wait_until_completed(FHIR_ENCOUNTER_ID, FHIR_PRACTITIONER_ID)
      if outcome == "COMPLETED":
          summary = retrieve_patient_summary(FHIR_ENCOUNTER_ID, FHIR_PRACTITIONER_ID)
          print(json.dumps(summary, indent=2))
      else:
          print("Terminal status:", outcome)
      ```
    </Tab>
  </Tabs>

  <Steps>
    <Step title="Poll encounter status">
      Call [Get patient summary generation status for encounter](/patient-summary-api-reference/summary-jobs/encounter-status) with the same `fhir_encounter_id` and `fhir_practitioner_id` you used to generate.
    </Step>

    <Step title="Wait for COMPLETED">
      Retry every few seconds while status is `READY` or `RUNNING`. Exit the loop when status is `COMPLETED`, `FAILED`, or `ABORTED`.
    </Step>

    <Step title="Poll by patient_summary_id (optional)">
      Alternatively, poll [Get patient summary job status](/patient-summary-api-reference/summary-jobs/job-status) with the stored `patient_summary_id`.
    </Step>

    <Step title="Retrieve the summary">
      Only after `COMPLETED`, call retrieve Patient Summary APIs for the encounter or job.
    </Step>
  </Steps>

  ## Try it

  <Card>
    * **Status progresses to COMPLETED**

      After generate, polling should move from `READY` or `RUNNING` to `COMPLETED`. Retrieve only after that transition.

    * **Terminal failures exit the loop**

      `FAILED` and `ABORTED` should stop polling. Do not call retrieve for those outcomes.

    * **Matching IDs across generate and poll**

      Using different encounter or practitioner IDs than generate returns the wrong job or a 404.
  </Card>

  ## Common mistakes

  <Warning>
    * Calling retrieve while status is still `READY` or `RUNNING`.
    * Stopping only on `COMPLETED` and ignoring `FAILED` / `ABORTED` (those loops never end cleanly).
    * Polling with different encounter or practitioner IDs than you used to generate.
  </Warning>

  ## Related cookbooks

  <Columns cols={2}>
    <Card title="Generate a Patient Summary" href="/documentation/cookbooks/generate-patient-summary-for-encounter" arrow={true} icon="book-open">
      Trigger generation and save the ID.
    </Card>

    <Card title="Ingest FHIR into CKG" href="/documentation/cookbooks/upload-fhir-to-ckg-and-poll" arrow={true} icon="book-open">
      Upload FHIR, then wait for COMPLETED.
    </Card>
  </Columns>
</CbRecipePage>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.