***

title: Artifacts
subtitle: Parse and render rich content delivered inline
slug: artifacts
---------------------

For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.sunnyhealthai.com/ask-sunny/llms.txt. For full documentation content, see https://docs.sunnyhealthai.com/ask-sunny/llms-full.txt.

Artifacts are rich content objects embedded in chat messages. Learn how the WebSocket delivers them JIT and how to parse and render artifacts like doctor profiles and custom data structures.

## What are artifacts?

Artifacts are structured data objects that provide additional context or information beyond plain text messages. Common examples include:

* **Doctor profiles**: Healthcare provider information with NPI, specialty, ratings, etc.
* **Provider search results**: Lists of providers from search queries with locations and distances
* **Structured data**: Custom data objects specific to your use case
* **Rich content**: Any JSON-serializable object

## How artifacts are delivered

Artifacts are **not fetched via REST API**. The WebSocket backend expands artifact tags just-in-time (JIT) during streaming. When the AI references an artifact, the backend replaces the tag with the full artifact JSON and embeds it directly in `message.text`.

You receive artifact content **inline**--no separate fetch is needed. The message text contains embedded JSON objects with `item_type`, `item_content`, and `created_at`.

## Parsing embedded artifacts

Parse `message.text` for JSON objects with `item_type` and `item_content`:

```typescript
function parseEmbeddedArtifacts(text: string): Array<{ item_type: string; item_content: unknown }> {
  const artifacts: Array<{ item_type: string; item_content: unknown }> = [];
  let i = 0;

  while (i < text.length) {
    const start = text.indexOf('{', i);
    if (start === -1) break;

    let braceCount = 0;
    let end = start;
    let inString = false;
    let escapeNext = false;

    for (let j = start; j < text.length; j++) {
      const char = text[j];
      if (escapeNext) { escapeNext = false; continue; }
      if (char === '\\') { escapeNext = true; continue; }
      if (char === '"') { inString = !inString; continue; }
      if (inString) continue;
      if (char === '{') braceCount++;
      else if (char === '}') {
        braceCount--;
        if (braceCount === 0) { end = j + 1; break; }
      }
    }

    if (braceCount === 0 && end > start) {
      try {
        const obj = JSON.parse(text.slice(start, end));
        if (obj && typeof obj === 'object' && obj.item_type && obj.item_content) {
          artifacts.push({ item_type: obj.item_type, item_content: obj.item_content });
        }
      } catch { /* skip invalid JSON */ }
      i = end;
    } else {
      i = start + 1;
    }
  }
  return artifacts;
}
```

## Artifact structure

The inline JSON format embedded in message text:

```typescript
interface InlineArtifact<T = unknown> {
  item_type: string;
  item_content: T;
  created_at?: string;
}
```

## Doctor profile artifacts

Doctor profiles are a common artifact type:

```typescript
interface DoctorProfileArtifact {
  npi: string;
  first_name?: string;
  last_name?: string;
  title?: string;
  specialty?: string;
  languages_spoken?: string[];
  gender?: string;
  rating?: number;
  review_count?: number;
  last_updated_at?: string;
  locations?: unknown;
  rank_score?: number;
  mrf_rates?: Array<Record<string, unknown>>;
  out_of_pocket_costs?: Array<{
    procedure_code: string;
    procedure_code_type?: string;
    procedure_name?: string;
    rate?: number;
    out_of_pocket: number;
  }>;
}
```

### Parsing doctor profiles from message text

```typescript
const artifacts = parseEmbeddedArtifacts(message.text);

artifacts.forEach(({ item_type, item_content }) => {
  if (item_type === "doctor_profile") {
    const profile = item_content as DoctorProfileArtifact;
    console.log("Name:", `${profile.first_name} ${profile.last_name}`);
    console.log("Specialty:", profile.specialty);
    console.log("Rating:", profile.rating);
    console.log("NPI:", profile.npi);
  }
});
```

## Provider search results artifacts

The `provider_search_results` artifact type contains search results from the provider search tool:

```typescript
interface ProviderSearchResultsArtifact {
  providers: ProviderResult[];
  query: string;
  location: string;
  taxonomy_codes: string[];
  plan_name: string;
  filter_gender: string | null;
  filter_languages: string[] | null;
}

interface ProviderResult {
  npi: string;
  name: string | null;
  specialties: string[];
  degrees: string[];
  languages: string[];
  locations: LocationResult[];
}

interface LocationResult {
  name: string | null;
  address_line_1: string | null;
  address_line_2: string | null;
  city: string | null;
  state: string | null;
  zip: string | null;
  distance_miles: number | null;
}
```

### Parsing provider search results

```typescript
const artifacts = parseEmbeddedArtifacts(message.text);

artifacts.forEach(({ item_type, item_content }) => {
  if (item_type === "provider_search_results") {
    const results = item_content as ProviderSearchResultsArtifact;
    console.log("Query:", results.query);
    results.providers.forEach((provider) => {
      console.log(provider.name, provider.npi, provider.specialties);
    });
  }
});
```

## Rendering artifacts

The `createSunnyChat` widget automatically parses and renders doctor profiles and provider search results from message text:

```typescript
import { createSunnyChat } from "@sunnyhealthai/agents-sdk";

const chat = createSunnyChat({
  container: document.getElementById("chat"),
  partnerIdentifier: "acme-health",
  publicKey: "pk-sunnyagents_abc_xyz",
  authType: "passwordless",
});
```

Doctor profiles and provider search results are automatically rendered when embedded in messages.

## Embedded artifact format

Message text contains JSON blobs--no `{art_tag}` tags are visible to the client. The backend expands `{art_tag}uuid{/art_tag}` server-side before streaming. What you receive looks like:

```
Here's a doctor who might work for you: {"item_type":"doctor_profile","item_content":{"npi":"1234567890","first_name":"Jane","last_name":"Smith","specialty":"Cardiology","rating":4.8,"review_count":150},"created_at":"2025-01-15T12:00:00Z"}
```

## Error handling

### Malformed JSON

When parsing, handle invalid JSON gracefully:

```typescript
try {
  const obj = JSON.parse(jsonStr);
  if (obj?.item_type && obj?.item_content) {
    // Valid artifact
  }
} catch {
  // Skip or show fallback for malformed JSON
}
```

## Best practices

1. **Parse defensively**: Wrap JSON.parse in try/catch; skip invalid objects
2. **Type safety**: Use TypeScript assertions or guards for `item_content` by `item_type`
3. **Fallback UI**: Provide fallback when an artifact fails to parse
4. **No loading states**: Artifacts arrive with the message--no fetch loading needed

## Next steps

* **[Key Concepts](/concepts)** - Understand events, state management, and MCP approvals
* **[Authentication](/authentication)** - Set up authenticated sessions
* **[API Reference](/api-reference)** - WebSocket schemas and artifact type definitions