CKG Data Ingestion
Request FHIR Bundle Upload URL
Request a pre-signed upload URL for uploading FHIR bundles to Suki’s Clinical Knowledge Graph (CKG)
Listen to this pageabout 1 min
GET
/
api
/
v1
/
fhir-push
/
upload-url
cURL
curl -G "https://sdp.suki-stage.com/api/v1/fhir-push/upload-url" \
--data-urlencode "organization_id=your-organization-id" \
--data-urlencode "correlation_id=encounter-abc-123" \
-H "Authorization: Bearer YOUR_SDP_SUKI_TOKEN"
import requests
import time
import json
BASE_URL = "https://sdp.suki.ai"
sdp_token = "<sdp_suki_token from login>"
org_id = "<your-organization-id>"
# Step 1: Request upload URL
params = {"organization_id": org_id, "correlation_id": "encounter-abc-123"}
headers = {"Authorization": f"Bearer {sdp_token}"}
resp = requests.get(
f"{BASE_URL}/api/v1/fhir-push/upload-url",
params=params,
headers=headers,
timeout=30
)
resp.raise_for_status()
data = resp.json()
transaction_id = data["transaction_id"]
upload_url = data["upload_url"]
print(f"Transaction ID: {transaction_id}")
# Step 2: Upload FHIR bundle
with open("my-fhir-bundle.json", "rb") as f:
upload_resp = requests.put(
upload_url,
headers={
"Content-Type": "application/json",
"x-goog-content-length-range": "0,524288000"
},
data=f,
timeout=300
)
upload_resp.raise_for_status()
print("Upload complete. Polling status...")
# Step 3: Poll status
while True:
status_resp = requests.get(
f"{BASE_URL}/api/v1/fhir-push/status/{transaction_id}",
headers=headers,
timeout=30
)
status_data = status_resp.json()
state = status_data["status"]
print(f"Status: {state}")
if state in ["COMPLETED", "FAILED", "ARCHIVED"]:
print(json.dumps(status_data, indent=2))
break
time.sleep(5)import fs from "fs";
const BASE_URL = "https://sdp.suki.ai";
const sdpSukiToken = "<sdp_suki_token from login>";
const orgId = "<your-organization-id>";
// Step 1: Request upload URL
const params = new URLSearchParams({
organization_id: orgId,
correlation_id: "encounter-abc-123",
});
const resp = await fetch(`${BASE_URL}/api/v1/fhir-push/upload-url?${params}`, {
headers: { Authorization: `Bearer ${sdpSukiToken}` },
});
if (!resp.ok) throw new Error(`Request failed: ${resp.status}`);
const data = await resp.json();
const transactionId = data.transaction_id;
const uploadUrl = data.upload_url;
console.log(`Transaction ID: ${transactionId}`);
// Step 2: Upload FHIR bundle
const bundleContent = fs.readFileSync("my-fhir-bundle.json", "utf-8");
const uploadResp = await fetch(uploadUrl, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-goog-content-length-range": "0,524288000",
},
body: bundleContent,
});
if (!uploadResp.ok) throw new Error(`Upload failed: ${uploadResp.status}`);
console.log("Upload complete. Polling status...");
// Step 3: Poll status
while (true) {
const statusResp = await fetch(
`${BASE_URL}/api/v1/fhir-push/status/${transactionId}`,
{ headers: { Authorization: `Bearer ${sdpSukiToken}` } }
);
const statusData = await statusResp.json();
console.log(`Status: ${statusData.status}`);
if (["COMPLETED", "FAILED", "ARCHIVED"].includes(statusData.status)) {
console.log(JSON.stringify(statusData, null, 2));
break;
}
await new Promise((resolve) => setTimeout(resolve, 5000));
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sdp.suki.ai/api/v1/fhir-push/upload-url",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"sdp_suki_token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://sdp.suki.ai/api/v1/fhir-push/upload-url"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("sdp_suki_token", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://sdp.suki.ai/api/v1/fhir-push/upload-url")
.header("sdp_suki_token", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://sdp.suki.ai/api/v1/fhir-push/upload-url")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["sdp_suki_token"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"transaction_id": "dea454fe-fecb-4cf0-aaa7-fa84d19c28f8",
"upload_url": "https://storage.googleapis.com/...",
"expires_at": "2026-05-18T06:55:42Z",
"correlation_id": "encounter-12345-upload-1"
}{
"code": 400,
"message": "invalid request"
}{
"code": 401,
"message": "invalid token"
}{
"code": 403,
"message": "forbidden"
}{
"code": 404,
"message": "not found"
}{
"code": 500,
"message": "internal server error"
}Use this endpoint to generate a pre-signed upload URL for your FHIR bundle. This is Step 1 of the CKG data ingestion workflow.
The response includes:
Once you have the upload URL, use it to upload your FHIR bundle and check the ingestion status by using the following endpoints:
Pass
organization_id as the partner organization ID you chose at provisioning for your organization. This can be a UUID or non-UUID string. But not a Suki UUID.upload_url: A Google Cloud Storage pre-signed URL that is valid for 15 minutes and can be used to upload the FHIR bundle.transaction_id: A unique identifier for the ingestion job. Use this value to check the ingestion status in Step 3.
To generate accurate Patient Summaries, include consistent patient, encounter, and practitioner identifiers in your FHIR resources so they match the
fhir_encounter_id and fhir_practitioner_id values used for generation and retrieval.CKG data ingestion steps
| Step | Action |
|---|---|
| 1 | Request upload URL - This API is the first step in the three-step CKG Data Ingestion workflow. |
| 2 | Upload FHIR bundle - Use PUT to upload your bundle API |
| 3 | Poll status - Check ingestion status API |
Authorizations
Suki access token (suki_token) from Login or Register. Expires after one hour.
Query Parameters
Your partner organization ID for this organization, chosen at provisioning (required). It can be a UUID or non-UUID string. But not a Suki UUID.
Optional client-defined identifier for ordering or correlating uploads (max 128 chars). If omitted the server generates one.
Maximum string length:
128Response
Upload URL returned successfully.
Unique identifier for this upload transaction.
Pre-signed URL to PUT your FHIR bundle to (expires in ~15 minutes).
RFC 3339 timestamp when the upload_url expires.
Echo of client-supplied correlation_id or server-generated value.
Last modified on September 3, 2026
Was this page helpful?
cURL
curl -G "https://sdp.suki-stage.com/api/v1/fhir-push/upload-url" \
--data-urlencode "organization_id=your-organization-id" \
--data-urlencode "correlation_id=encounter-abc-123" \
-H "Authorization: Bearer YOUR_SDP_SUKI_TOKEN"
import requests
import time
import json
BASE_URL = "https://sdp.suki.ai"
sdp_token = "<sdp_suki_token from login>"
org_id = "<your-organization-id>"
# Step 1: Request upload URL
params = {"organization_id": org_id, "correlation_id": "encounter-abc-123"}
headers = {"Authorization": f"Bearer {sdp_token}"}
resp = requests.get(
f"{BASE_URL}/api/v1/fhir-push/upload-url",
params=params,
headers=headers,
timeout=30
)
resp.raise_for_status()
data = resp.json()
transaction_id = data["transaction_id"]
upload_url = data["upload_url"]
print(f"Transaction ID: {transaction_id}")
# Step 2: Upload FHIR bundle
with open("my-fhir-bundle.json", "rb") as f:
upload_resp = requests.put(
upload_url,
headers={
"Content-Type": "application/json",
"x-goog-content-length-range": "0,524288000"
},
data=f,
timeout=300
)
upload_resp.raise_for_status()
print("Upload complete. Polling status...")
# Step 3: Poll status
while True:
status_resp = requests.get(
f"{BASE_URL}/api/v1/fhir-push/status/{transaction_id}",
headers=headers,
timeout=30
)
status_data = status_resp.json()
state = status_data["status"]
print(f"Status: {state}")
if state in ["COMPLETED", "FAILED", "ARCHIVED"]:
print(json.dumps(status_data, indent=2))
break
time.sleep(5)import fs from "fs";
const BASE_URL = "https://sdp.suki.ai";
const sdpSukiToken = "<sdp_suki_token from login>";
const orgId = "<your-organization-id>";
// Step 1: Request upload URL
const params = new URLSearchParams({
organization_id: orgId,
correlation_id: "encounter-abc-123",
});
const resp = await fetch(`${BASE_URL}/api/v1/fhir-push/upload-url?${params}`, {
headers: { Authorization: `Bearer ${sdpSukiToken}` },
});
if (!resp.ok) throw new Error(`Request failed: ${resp.status}`);
const data = await resp.json();
const transactionId = data.transaction_id;
const uploadUrl = data.upload_url;
console.log(`Transaction ID: ${transactionId}`);
// Step 2: Upload FHIR bundle
const bundleContent = fs.readFileSync("my-fhir-bundle.json", "utf-8");
const uploadResp = await fetch(uploadUrl, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"x-goog-content-length-range": "0,524288000",
},
body: bundleContent,
});
if (!uploadResp.ok) throw new Error(`Upload failed: ${uploadResp.status}`);
console.log("Upload complete. Polling status...");
// Step 3: Poll status
while (true) {
const statusResp = await fetch(
`${BASE_URL}/api/v1/fhir-push/status/${transactionId}`,
{ headers: { Authorization: `Bearer ${sdpSukiToken}` } }
);
const statusData = await statusResp.json();
console.log(`Status: ${statusData.status}`);
if (["COMPLETED", "FAILED", "ARCHIVED"].includes(statusData.status)) {
console.log(JSON.stringify(statusData, null, 2));
break;
}
await new Promise((resolve) => setTimeout(resolve, 5000));
}<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sdp.suki.ai/api/v1/fhir-push/upload-url",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"sdp_suki_token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://sdp.suki.ai/api/v1/fhir-push/upload-url"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("sdp_suki_token", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://sdp.suki.ai/api/v1/fhir-push/upload-url")
.header("sdp_suki_token", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://sdp.suki.ai/api/v1/fhir-push/upload-url")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["sdp_suki_token"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"transaction_id": "dea454fe-fecb-4cf0-aaa7-fa84d19c28f8",
"upload_url": "https://storage.googleapis.com/...",
"expires_at": "2026-05-18T06:55:42Z",
"correlation_id": "encounter-12345-upload-1"
}{
"code": 400,
"message": "invalid request"
}{
"code": 401,
"message": "invalid token"
}{
"code": 403,
"message": "forbidden"
}{
"code": 404,
"message": "not found"
}{
"code": 500,
"message": "internal server error"
}