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

# Retrieve Results from E/M Coding Webhook

> Use standalone E/M Coding webhook links to retrieve recommended codes and check session status

When Suki sends a `SUCCESS` standalone E/M Coding webhook, the payload includes links to the recommended codes and the session status.

> [!NOTE]
>
> The webhook does not contain the codes themselves. Your application must call the linked E/M Coding API endpoints to retrieve them.
>
> This page covers **standalone** E/M Coding only. Codes requested on an ambient visit with `artifacts: ["EM_CODES"]` arrive on the [Ambient webhook](https://developer.suki.ai/documentation/webhook/ambient/overview.md), not this one.

Use the `em_codes` link to load the recommended codes. Call the `status` link when you need to confirm coding finished. Each `href` is relative to `/api/v1`. Join your API base URL with that path, and authenticate the request with your Suki Token.

## Prerequisites

Before you retrieve results, make sure you have:

* **A webhook receiver:** An endpoint that receives Suki webhooks and verifies their signatures. Refer to [Webhook quickstart](https://developer.suki.ai/documentation/webhook/quickstart.md) and [Signature verification](https://developer.suki.ai/documentation/webhook/signature-verification.md).
* **A valid Suki Token:** Authenticate E/M Coding API requests with the `sdp_suki_token` header. Include the `sdp_provider_id` header when your partnership requires it. Refer to [E/M Coding API quickstart](https://developer.suki.ai/em-coding-api-reference/quickstart.md).
* **The E/M Coding `session_id`:** Save the session ID returned by [Generate E/M codes](https://developer.suki.ai/em-coding-api-reference/em-coding-sessions/generate.md). That ID is never an ambient session ID.

## Build the full URL

Each `href` in an E/M Coding webhook is relative to `/api/v1`. Append `/api/v1` and the `href` to your API base URL.

For example, when the production base URL is `https://sdp.suki.ai` and the link contains:

```text theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
/em-codes/{session_id}
```

the full request URL is:

```text theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
https://sdp.suki.ai/api/v1/em-codes/{session_id}
```

For staging, use `https://sdp.suki-stage.com` as the API base URL.

### Available endpoints

| `_links` key | Endpoint | API reference |
| :- | :- | :- |
| `em_codes` | `GET /api/v1/em-codes/{session_id}` | [Get E/M codes](https://developer.suki.ai/em-coding-api-reference/em-coding-sessions/get.md) |
| `status` | `GET /api/v1/em-codes/{session_id}/status` | [Get E/M Coding status](https://developer.suki.ai/em-coding-api-reference/em-coding-sessions/status.md) |

Every link object includes a `method` field. Use that method when you call the endpoint. For these links, the method is always `GET`.

Each of these keys holds exactly one link for the standalone session. Match the link's `name` to the webhook's `session_id`, or use the first entry in the array.

> [!NOTE]
>
> Other `_links` keys are present on the E/M Coding webhook but are empty arrays. Ignore them. Standalone E/M Coding sessions are not tied to an encounter, so `encounter_id` is an empty string. Refer to [E/M Coding webhook payload](https://developer.suki.ai/documentation/webhook/em-coding/payload.md#the-_links-object).

## Retrieve results step by step

Follow this sequence after your webhook endpoint receives a `SUCCESS` notification:

**1. Verify the Webhook and Return HTTP 200**

Verify the webhook signature and return HTTP **200** within 30 seconds. Do not wait for E/M Coding API calls to finish before you respond. Process the notification and retrieve results in a background job.

**2. Select the Links for This Session**

Read `_links.em_codes` and `_links.status`. Match each link's `name` to the webhook's `session_id`.

**3. Check Session Status When Needed**

Call the `status` link when you need to confirm coding finished. Continue to retrieve codes when the returned status is `completed`. If the status is `failed`, do not expect codes on the retrieve endpoint.

**4. Retrieve and Store the Codes**

Call the `em_codes` link and read `em_codes.values`. Store the codes you need soon after the webhook arrives. Standalone codes stay available for **30 days**.

> [!WARNING]
>
> After **30 days**, [Get E/M codes](https://developer.suki.ai/em-coding-api-reference/em-coding-sessions/get.md) returns `404` with `em codes for session older than 30 days is not available`. Read and store the codes when the webhook arrives.

## Read the recommended codes

| Field | Description |
| :- | :- |
| `em_codes.values` | Array of recommended codes. Each item can include `code`, `family`, `level`, `patient_type`, optional modifiers, and exactly one feature set: `mdm_based_features`, `time_based_features`, or `wellness_features`. |

For a full example response and field mapping, refer to [Get the generated E/M codes](https://developer.suki.ai/documentation/how-to/em-coding/retrieve-em-codes.md).

## Check session status

Call [Get E/M Coding status](https://developer.suki.ai/em-coding-api-reference/em-coding-sessions/status.md) when you need the current processing state.

| Status | Description |
| :- | :- |
| `created` | Session accepted and not finished processing. |
| `running` | Coding is in progress. |
| `completed` | Codes are ready. Call Get E/M codes. |
| `failed` | Coding failed. Do not expect codes on the retrieve endpoint. |

## Example: retrieve recommended codes

The following examples retrieve codes for the standalone E/M Coding session that triggered the webhook.

Run this code after your webhook endpoint has verified the signature and returned HTTP **200**. The examples:

1. Read `session_id` and `_links` from the webhook payload.
2. Find the `em_codes` link for that session.
3. Retrieve `em_codes.values`.

The examples use the production API base URL. Replace it with the staging base URL when you test against staging.

**Language tabs (agents):** Equivalent code samples are available in: Python, TypeScript, Go. Humans see one language at a time. Use the variant that matches the user's stack; behavior is the same across tabs.

### Code example

> [tab] Python
>
> ```python expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> import requests
>
> BASE_URL = "https://sdp.suki.ai"
> API_PATH = "/api/v1"
> HEADERS = {"sdp_suki_token": "<sdp_suki_token>"}
>
>
> def link_for(links, key, name):
>     for link in links.get(key) or []:
>         if link.get("name") == name:
>             return link
>     return None
>
>
> def load_em_codes(payload):
>     session_id = payload["session_id"]
>     links = payload.get("_links") or {}
>
>     codes_link = link_for(links, "em_codes", session_id)
>     if not codes_link:
>         raise ValueError(f"No em_codes link found for session {session_id}")
>
>     response = requests.get(
>         BASE_URL + API_PATH + codes_link["href"],
>         headers=HEADERS,
>     )
>     response.raise_for_status()
>
>     values = response.json()["em_codes"].get("values") or []
>     for item in values:
>         print(f"{item.get('code')} ({item.get('family')}, level {item.get('level')})")
>     return values
> ```

> [tab] TypeScript
>
> ```typescript expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> const BASE_URL = "https://sdp.suki.ai";
> const API_PATH = "/api/v1";
> const HEADERS = { sdp_suki_token: "<sdp_suki_token>" };
>
> type Link = {
>   href: string;
>   method: string;
>   name: string;
>   type: string;
> };
>
> function linkFor(
>   links: Record<string, Link[]>,
>   key: string,
>   name: string
> ): Link | undefined {
>   return (links[key] ?? []).find((link) => link.name === name);
> }
>
> export async function loadEmCodes(payload: any) {
>   const sessionId: string = payload.session_id;
>   const links: Record<string, Link[]> = payload._links ?? {};
>
>   const codesLink = linkFor(links, "em_codes", sessionId);
>   if (!codesLink) {
>     throw new Error(`No em_codes link found for session ${sessionId}`);
>   }
>
>   const response = await fetch(BASE_URL + API_PATH + codesLink.href, {
>     headers: HEADERS,
>   });
>   if (!response.ok) {
>     throw new Error(`E/M codes call failed: ${response.status}`);
>   }
>
>   const { em_codes } = await response.json();
>   const values = em_codes.values ?? [];
>   for (const item of values) {
>     console.log(`${item.code} (${item.family}, level ${item.level})`);
>   }
>   return values;
> }
> ```

> [tab] Go
>
> ```go expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> package main
>
> import (
> 	"encoding/json"
> 	"fmt"
> 	"io"
> 	"net/http"
> )
>
> const (
> 	baseURL   = "https://sdp.suki.ai"
> 	apiPath   = "/api/v1"
> 	sukiToken = "<sdp_suki_token>"
> )
>
> type link struct {
> 	Href   string `json:"href"`
> 	Method string `json:"method"`
> 	Name   string `json:"name"`
> 	Type   string `json:"type"`
> }
>
> func linkFor(links map[string][]link, key, name string) (link, bool) {
> 	for _, item := range links[key] {
> 		if item.Name == name {
> 			return item, true
> 		}
> 	}
> 	return link{}, false
> }
>
> func getJSON(path string) (map[string]any, error) {
> 	req, err := http.NewRequest(http.MethodGet, baseURL+apiPath+path, nil)
> 	if err != nil {
> 		return nil, err
> 	}
> 	req.Header.Set("sdp_suki_token", sukiToken)
>
> 	res, err := http.DefaultClient.Do(req)
> 	if err != nil {
> 		return nil, err
> 	}
> 	defer res.Body.Close()
>
> 	body, err := io.ReadAll(res.Body)
> 	if err != nil {
> 		return nil, err
> 	}
> 	if res.StatusCode < 200 || res.StatusCode >= 300 {
> 		return nil, fmt.Errorf("request failed: %s", res.Status)
> 	}
>
> 	var payload map[string]any
> 	if err := json.Unmarshal(body, &payload); err != nil {
> 		return nil, err
> 	}
> 	return payload, nil
> }
>
> func loadEmCodes(payload map[string]any) ([]any, error) {
> 	sessionID, _ := payload["session_id"].(string)
> 	rawLinks, _ := payload["_links"].(map[string]any)
>
> 	links := map[string][]link{}
> 	for key, value := range rawLinks {
> 		encoded, err := json.Marshal(value)
> 		if err != nil {
> 			return nil, err
> 		}
> 		var items []link
> 		if err := json.Unmarshal(encoded, &items); err != nil {
> 			return nil, err
> 		}
> 		links[key] = items
> 	}
>
> 	codesLink, ok := linkFor(links, "em_codes", sessionID)
> 	if !ok {
> 		return nil, fmt.Errorf("no em_codes link found for session %s", sessionID)
> 	}
>
> 	response, err := getJSON(codesLink.Href)
> 	if err != nil {
> 		return nil, err
> 	}
>
> 	emCodes, _ := response["em_codes"].(map[string]any)
> 	values, _ := emCodes["values"].([]any)
> 	return values, nil
> }
> ```

## Example: check session status

Reuse the same base URL, path prefix, headers, and `link_for` helper. Change only the `_links` key to `status`.

**Language tabs (agents):** Equivalent code samples are available in: Python, TypeScript, Go. Humans see one language at a time. Use the variant that matches the user's stack; behavior is the same across tabs.

### Code example

> [tab] Python
>
> ```python expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> def get_em_coding_status(links, session_id):
>     status_link = link_for(links, "status", session_id)
>     if not status_link:
>         raise ValueError(f"No status link found for session {session_id}")
>
>     response = requests.get(
>         BASE_URL + API_PATH + status_link["href"],
>         headers=HEADERS,
>     )
>     response.raise_for_status()
>     return response.json().get("status")
> ```

> [tab] TypeScript
>
> ```typescript expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> export async function getEmCodingStatus(
>   links: Record<string, Link[]>,
>   sessionId: string
> ) {
>   const statusLink = linkFor(links, "status", sessionId);
>   if (!statusLink) {
>     throw new Error(`No status link found for session ${sessionId}`);
>   }
>
>   const response = await fetch(BASE_URL + API_PATH + statusLink.href, {
>     headers: HEADERS,
>   });
>   if (!response.ok) {
>     throw new Error(`Status call failed: ${response.status}`);
>   }
>   const data = await response.json();
>   return data.status as string;
> }
> ```

> [tab] Go
>
> ```go expandable theme={"theme":{"light":"one-light","dark":"material-theme-darker"}}
> func getEmCodingStatus(links map[string][]link, sessionID string) (string, error) {
> 	statusLink, ok := linkFor(links, "status", sessionID)
> 	if !ok {
> 		return "", fmt.Errorf("no status link found for session %s", sessionID)
> 	}
> 	payload, err := getJSON(statusLink.Href)
> 	if err != nil {
> 		return "", err
> 	}
> 	status, _ := payload["status"].(string)
> 	return status, nil
> }
> ```

## Responses from linked endpoints

When you call the E/M Coding API endpoints, Suki returns standard HTTP status codes.

| HTTP status | Meaning | Example response |
| :- | :- | :- |
| `200` | OK. The request succeeded. | |
| `400` | Bad Request. The request was invalid. | |
| `401` | Unauthorized. The token is invalid or authentication failed. | |
| `404` | Not Found. The session does not exist, or its codes are older than 30 days. | |
| `500` | Internal Server Error. | |

For the exact error messages, refer to [E/M Coding error messages](https://developer.suki.ai/em-coding-api-reference/error-messages-em-coding.md).

* **`400 Bad Request`:** Fix the URL or request parameters, then send the request again.
* **`401 Unauthorized`:** Log in again for a new Suki Token, then retry with the updated token.
* **`404 Not Found`:** Confirm the E/M Coding `session_id`, or store codes before the 30-day window ends.
* **`5xx Server Error`:** Retry the request after a short delay.

## Next steps

&#x20;**[E/M Coding payload reference](https://developer.suki.ai/documentation/webhook/em-coding/payload.md)** - Every field and link key in the webhook body.

&#x20;**[Get the generated E/M codes](https://developer.suki.ai/documentation/how-to/em-coding/retrieve-em-codes.md)** - Read each code and its reason.

&#x20;**[E/M Coding overview](https://developer.suki.ai/documentation/concepts/em-coding/overview.md)** - How E/M Coding fits your workflow.

&#x20;**[Webhook request and response](https://developer.suki.ai/documentation/webhook/payload-and-response.md)** - The 200 response, retries, and duplicate deliveries.


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