> ## 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.

# Upload FHIR to CKG and Poll Ingestion

> Request a pre-signed upload URL, PUT a FHIR bundle, then poll ingestion until COMPLETED

**Problem:** Patient Summary generation starts before Clinical Knowledge Graph (CKG) ingestion finishes, so summaries are empty or fail.

**Solution:** Request an upload URL with [Request upload URL](/patient-summary-api-reference/ckg-data-ingestion/upload), PUT the FHIR bundle, then poll [Check ingestion status](/patient-summary-api-reference/ckg-data-ingestion/ingestion-status) until `COMPLETED`. Treat `FAILED` and `ARCHIVED` as terminal.

<Note>
  This cookbook assumes you already have your Partner ID, a Bearer JWT for CKG upload, `organization_id`, and a valid FHIR R4 bundle JSON file.
</Note>

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    import fs from "fs";

    const BASE_URL = "https://sdp.suki.ai";

    async function uploadAndWaitForCkg(organizationId: string, bundlePath: string) {
      const params = new URLSearchParams({
        organization_id: organizationId,
        correlation_id: "encounter-abc-123",
      });

      const urlRes = await fetch(`${BASE_URL}/api/v1/fhir-push/upload-url?${params}`, {
        headers: { Authorization: `Bearer ${sdpJwtBearerToken}` },
      });
      if (!urlRes.ok) {
        throw new Error(`Request upload URL failed: ${urlRes.status}`);
      }

      const { transaction_id: transactionId, upload_url: uploadUrl } = await urlRes.json();
      const bundle = fs.readFileSync(bundlePath, "utf-8");

      const uploadRes = await fetch(uploadUrl, {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          "x-goog-content-length-range": "0,524288000",
        },
        body: bundle,
      });
      if (!uploadRes.ok) {
        throw new Error(`FHIR upload failed: ${uploadRes.status}`);
      }

      for (let i = 0; i < 120; i++) {
        const statusRes = await fetch(
          `${BASE_URL}/api/v1/fhir-push/status/${transactionId}`,
          { headers: { Authorization: `Bearer ${sdpJwtBearerToken}` } }
        );
        if (!statusRes.ok) {
          throw new Error(`Get ingestion status failed: ${statusRes.status}`);
        }

        const { status } = await statusRes.json();
        if (status === "COMPLETED") return "COMPLETED";
        if (status === "FAILED" || status === "ARCHIVED") return status;

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

      throw new Error("Timed out waiting for CKG ingestion");
    }

    const outcome = await uploadAndWaitForCkg(organizationId, "my-fhir-bundle.json");
    if (outcome === "COMPLETED") {
      // Generate Patient Summaries
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    import time
    import requests

    BASE_URL = "https://sdp.suki.ai"

    def upload_and_wait_for_ckg(organization_id: str, bundle_path: str) -> str:
        url_res = requests.get(
            f"{BASE_URL}/api/v1/fhir-push/upload-url",
            params={
                "organization_id": organization_id,
                "correlation_id": "encounter-abc-123",
            },
            headers={"Authorization": f"Bearer {sdp_jwt_bearer_token}"},
            timeout=30,
        )
        url_res.raise_for_status()
        data = url_res.json()
        transaction_id = data["transaction_id"]
        upload_url = data["upload_url"]

        with open(bundle_path, "rb") as f:
            upload_res = requests.put(
                upload_url,
                headers={
                    "Content-Type": "application/json",
                    "x-goog-content-length-range": "0,524288000",
                },
                data=f,
                timeout=300,
            )
        upload_res.raise_for_status()

        for _ in range(120):
            status_res = requests.get(
                f"{BASE_URL}/api/v1/fhir-push/status/{transaction_id}",
                headers={"Authorization": f"Bearer {sdp_jwt_bearer_token}"},
                timeout=30,
            )
            status_res.raise_for_status()
            status = status_res.json().get("status")
            if status == "COMPLETED":
                return "COMPLETED"
            if status in ("FAILED", "ARCHIVED"):
                return status
            time.sleep(5)

        raise TimeoutError("Timed out waiting for CKG ingestion")

    outcome = upload_and_wait_for_ckg(organization_id, "my-fhir-bundle.json")
    if outcome == "COMPLETED":
        # Generate Patient Summaries
        pass
    ```
  </Tab>
</Tabs>

## Common mistakes

* Starting generation before ingestion status is `COMPLETED`.
* Letting the upload URL expire (**15 minutes**) before the PUT.
* Using different identifiers in the FHIR bundle than later `fhir_encounter_id` / `fhir_practitioner_id` values.
* Sending `sdp_suki_token` on CKG upload instead of the Bearer JWT.

## Other cookbooks

<div className="cookbook-hub-wrap">
  <div className="hp-io-method-grid tut-hub-card-grid" data-cookbook-related-grid>
    <a className="hp-io-method-card tut-hub-method-card" href="/documentation/cookbooks/generate-patient-summary-for-encounter">
      <div className="tut-hub-card-media" aria-hidden="true" />

      <div className="hp-io-method-card-body">
        <div className="tut-hub-card-badges">
          <span className="hp-wn-badge hp-wn-badge-new">Patient Summary</span>
          <span className="hp-wn-badge cookbook-hub-badge-surface cookbook-hub-badge-surface--api">API</span>
        </div>

        <h3 className="hp-io-method-card-title">Generate a Patient Summary for One Encounter</h3>

        <p className="hp-io-method-card-desc cookbook-hub-card-desc">
          Trigger generation and save the ID.
        </p>

        <div className="hp-io-method-card-meta tut-hub-card-foot" aria-label="5 min">
          <div className="tut-hub-card-foot-meta">
            <span className="hp-io-method-card-meta-time">5 min</span>
          </div>
        </div>
      </div>
    </a>

    <a className="hp-io-method-card tut-hub-method-card" href="/documentation/cookbooks/poll-patient-summary-status-before-retrieve">
      <div className="tut-hub-card-media tut-hub-card-media--blue" aria-hidden="true" />

      <div className="hp-io-method-card-body">
        <div className="tut-hub-card-badges">
          <span className="hp-wn-badge hp-wn-badge-new">Patient Summary</span>
          <span className="hp-wn-badge cookbook-hub-badge-surface cookbook-hub-badge-surface--api">API</span>
        </div>

        <h3 className="hp-io-method-card-title">Poll Patient Summary Status Before Retrieve</h3>

        <p className="hp-io-method-card-desc cookbook-hub-card-desc">
          Poll until status is COMPLETED.
        </p>

        <div className="hp-io-method-card-meta tut-hub-card-foot" aria-label="5 min">
          <div className="tut-hub-card-foot-meta">
            <span className="hp-io-method-card-meta-time">5 min</span>
          </div>
        </div>
      </div>
    </a>
  </div>
</div>
