Skip to Content
Overview

API Reference

The Rosetta API follows REST conventions. It accepts and returns JSON, uses standard HTTP methods and status codes, and authenticates via bearer tokens.

You can use the Rosetta API in sandbox mode, which doesn’t affect your live documents or interact with production clinical workflows. The API key you use to authenticate the request determines whether the request is live mode or sandbox mode.

The Rosetta API doesn’t support bulk updates. You can work on only one resource per request.


Just getting started?

View the development quickstart guide.


Base URL

https://philipshih.org/apps/rosetta/api/v1

Authentication

The Rosetta API uses bearer tokens to authenticate requests. Include your API key in the Authorization header of each request. You can manage your API keys from your account dashboard.

Your API keys carry privileges, so keep them secure. Do not share your secret API keys in publicly accessible areas such as GitHub or client-side code.

All API requests must be made over HTTPS. Calls made over plain HTTP will be rejected. API requests without authentication will return a 401 error.

curl https://philipshih.org/apps/rosetta/api/v1/documents \ -H "Authorization: Bearer sk_test_your_api_key"

Client Libraries

Official client libraries are available for Python and TypeScript.

# Python pip install rosetta-sdk # TypeScript / Node.js npm install @rosetta/sdk
import rosetta client = rosetta.Client(api_key="sk_test_your_api_key") doc = client.documents.create( title="Progress Note", template="soap", content="Patient presents with..." ) print(doc.id)
import Rosetta from "@rosetta/sdk"; const client = new Rosetta({ apiKey: "sk_test_your_api_key" }); const doc = await client.documents.create({ title: "Progress Note", template: "soap", content: "Patient presents with...", }); console.log(doc.id);

Getting Started

Sign in, set up your profile, and write your first note. Takes about two minutes.

Prerequisites

  • A current version of Chrome, Firefox, Safari, or Edge.

First Time Setup

1. Configure Your Profile

On first sign-in, set your name, role (Physician, Nurse, Resident), and specialty. Rosetta uses these to pick which templates and agents to show by default.

2. Learn the Interface

  • Header: template picker, save, export.
  • Editor: the document you’re writing.
  • Sidebar: Chat, version history, quick actions.

Basic Workflow

  1. Create a new document or start from a template.
  2. Write in the editor.
  3. Open Chat with Ctrl/Cmd + K to expand, format, or cite a passage.
  4. Review each pending suggestion and accept or reject it.
  5. Save or export. Export is blocked if required template fields are empty.

Keyboard Shortcuts

ShortcutAction
Ctrl/Cmd + SSave document
Ctrl/Cmd + NNew document
Ctrl/Cmd + KOpen Chat
/Command palette
Ctrl/Cmd + BBold text
Ctrl/Cmd + IItalic text

First Document

Three things you’ll use on most notes: version history, right-click actions on selected text, and Chat with citations.

Version History

Every save creates a new version. Open history from the header to compare any saved version to your current draft, and restore it if you want. Restoring also creates a new version, so the draft you were comparing against stays in the list.

Right-Click Actions

Select text and right-click to run an agent on just that selection (explain, expand, simplify, cite). The agent returns a pending suggestion. Your original text stays until you accept or reject it.

RAG Workflow

Ask Chat a clinical question. The Citations agent pulls supporting passages from your library and from the literature. Review the sources, pick the passages you want to use, and insert them. Each insertion brings its citation along.

Frequently Asked Questions

General

What is Rosetta?

A clinical text editor with built-in agents. You write notes as usual. Agents do the tedious work: expanding abbreviations, pulling references, formatting citations, drafting reasoning. Every change an agent proposes has to be accepted before it modifies your document.

Who can use Rosetta?

Available to physicians, residents, fellows, medical students at UCSF, and healthcare institutions.

How much does Rosetta cost?

Free during beta. Pricing TBA.

What browsers are supported?

Chrome 90+, Firefox 88+, Safari 14+, Edge 90+.

Getting Started

How do I get access?

Sign in at philipshih.org/apps/rosetta .

Can I import existing documents?

Yes. Supported formats: .docx, .rtf, .txt, .md. Use File -> Import.

Using Rosetta

What templates are available?

H&P, Progress Notes (SOAP), Consult Notes, Discharge Summaries, Procedure Notes, ER Notes, and specialty templates. You can also create custom templates.

How do I use Chat tools?

Press Ctrl/Cmd + K to ask a free-form question. Or select text and right-click to run an agent on just that selection (explain, expand, simplify, cite). In both cases the result comes back as a pending suggestion you accept or reject.

How do I export a document?

Click Export, choose a format (PDF, DOCX, HTML, or plain text), and download.

Is there an API?

Yes. REST API for documents, templates, tool requests, and exports. View API docs.


Didn’t find your answer? Contact: philip@philipshih.org

Editor

The editor is built on Lexical 0.39. Documents are stored as a node tree. Agent edits show up as pending suggestions first; you accept or reject each one before it changes the document. Template sections can be locked so agents can’t write to them.

Components

AreaWhat it does
EditorCoreMounts Lexical, tracks selection, dispatches commands.
NoteEditorV2Sets up the composer, loads plugins, wraps every change in a transaction.
TemplateManagerHolds template anchors and fields. Blocks writes to locked regions.
SuggestionOverlayShows pending agent diffs and the accept/reject controls.
SyncBridgeWrites to the local store and pushes to the cloud.

Runtime Topology

Core Rules

  1. All document mutations go through editor.update(). There is no other way to write to the document, so every change is transactional and observable.
  2. Agents and automation cannot modify locked template anchors. Only a user edit can.
  3. Each suggestion is tagged with the revision it was generated against. If the user edits in between, the suggestion’s range is remapped onto the new revision, or marked stale if remapping can’t resolve it.
  4. Undo/redo only covers changes that actually landed. Rejected and stale suggestions never wrote to the document, so they don’t appear in history.
  5. Every applied change, whether from a user or an agent, emits the same change event. Sync, logging, and other subscribers read from one stream.

Document Model

Documents are serialized as node trees, not HTML strings:

interface SerializedEditorState { root: { type: "root"; version: number; children: SerializedNode[]; direction: "ltr" | "rtl" | null; }; } interface SerializedNode { type: string; version: number; children?: SerializedNode[]; text?: string; format?: number; detail?: number; mode?: "normal" | "segmented" | "token"; }

Node Types

class RosettaTextNode extends TextNode { __format: number; } class RosettaParagraphNode extends ParagraphNode { __indent: number; } class AnchorNode extends ElementNode { __label: string; __isLocked: boolean; } class FieldNode extends DecoratorNode<JSX.Element> { __fieldType: "SELECT" | "MULTISELECT" | "DYNAMIC_SELECT"; __options: string[]; }

Change Tracking

Rosetta keeps two separate records of changes:

  • Lexical history stack: what undo/redo walks through. Only contains changes that were applied.
  • Operational log: what sync and agent bookkeeping read from. Contains every transaction with its source, revision, and ranges.

Transaction Shape

type ChangeSource = "user" | "ai" | "template" | "sync"; type ChangeKind = "insert" | "replace" | "delete" | "format"; interface ChangeTransaction { id: string; revision: number; parentRevision: number; source: ChangeSource; kind: ChangeKind; createdAt: number; ranges: Array<{ start: number; end: number; beforeText: string; afterText: string; anchorId?: string; }>; metadata?: { suggestionId?: string; agent?: "chat" | "shorthander" | "reasoner" | "reformatter" | "citations" | "ingester"; batched?: boolean; }; }

Keystroke-To-Commit Flow

Diff Strategy

The diff runs in three passes. Each pass only runs if the previous one didn’t produce a usable result:

  1. Node-level: compare subtrees by identity. Unchanged subtrees are skipped.
  2. Token-level: diff by sentence or field chunk. Most edits stop here.
  3. Character-level: used when token precision drops below 98%. Needed so overlays can highlight the exact changed characters.
function computeRanges(prev: string, next: string): RangeDelta[] { const tokenPass = diffByTokens(prev, next); if (tokenPass.precision >= 0.98) return tokenPass.ranges; return diffByCharacters(prev, next).ranges; }

Range Remapping

A suggestion points at specific character offsets. If the user types before the suggestion is applied, those offsets now point at the wrong place. The fix is to shift each offset by the net length change of every user edit that happened at or before it:

function remapRange(range: { start: number; end: number }, deltas: RangeDelta[]) { let { start, end } = range; for (const delta of deltas) { if (delta.pos <= start) start += delta.netLength; if (delta.pos < end) end += delta.netLength; } return { start, end }; }

Overlap and Conflict Rules

Agent Suggestion Lifecycle

Suggestion Object

type SuggestionStatus = | "pending" | "focused" | "accepted" | "rejected" | "stale" | "superseded"; interface AISuggestion { id: string; revision: number; type: "replace" | "insert" | "delete"; start: number; end: number; replacementText: string; reasoning: string; confidence: number; status: SuggestionStatus; }

Suggestion State Machine

Safe Apply Logic

function applySuggestion(suggestion: AISuggestion) { editor.update(() => { if (isLockedRange(suggestion.start, suggestion.end)) return; const remapped = remapSuggestionAgainstLatestRevision(suggestion); if (!remapped) { markSuggestion(suggestion.id, "stale"); return; } replaceText(remapped.start, remapped.end, remapped.replacementText); appendChangeTransaction({ source: "ai", kind: suggestion.type === "insert" ? "insert" : "replace", metadata: { suggestionId: suggestion.id } }); markSuggestion(suggestion.id, "accepted"); }); }

Visual Indicators

TypeColorBehavior
InsertGreenPreview as additive text
ReplaceYellowOriginal highlighted with replacement preview
DeleteRedOriginal shown with remove indicator

Template Anchors

interface TemplateAnchor { id: string; label: string; isLocked: boolean; nodeKey: string; start: number; end: number; }

Guard Rails

  • Agents cannot modify locked anchors.
  • Bulk accept skips suggestions touching locked anchors.
  • Template updates can move anchors but cannot silently unlock them.
  • Anchor deletion requires explicit user confirmation.

Anchor Navigation

ShortcutAction
Ctrl + ]Next anchor
Ctrl + [Previous anchor
Ctrl + EnterFill current anchor and advance

Template Fields

SELECT single choice:

{{SELECT: medication | aspirin, clopidogrel, warfarin}}

MULTISELECT multiple values:

{{MULTISELECT: symptoms | chest pain, dyspnea, diaphoresis}}

DYNAMIC_SELECT options resolved from query:

{{DYNAMIC_SELECT: drug | query: medications for hypertension}}

Field Node Rendering

class FieldNode extends DecoratorNode<JSX.Element> { decorate(): JSX.Element { return ( <FieldComponent type={this.__fieldType} options={this.__options} onSelect={this.handleSelect} /> ); } }

SmartPhrases

Expansion rules for high-frequency sections:

const smartPhrases: SmartPhrase[] = [ { trigger: ".cc", expansion: "Chief Complaint:\n" }, { trigger: ".hpi", expansion: "History of Present Illness:\n" }, { trigger: ".pe", expansion: "Physical Examination:\n" }, { trigger: ".ap", expansion: "Assessment & Plan:\n" } ];

Expansion is treated as a user transaction. Pending agent suggestions touching the trigger span are remapped; if remap fails, they are marked stale.

Plugin Order

<LexicalComposer initialConfig={config}> <RichTextPlugin /> <HistoryPlugin /> <OnChangePlugin onChange={handleChange} /> <TemplatePlugin /> <SmartPhrasePlugin /> <SuggestionPlugin /> <AutocompletePlugin /> </LexicalComposer>

TemplatePlugin before SuggestionPlugin so locks are enforced at preview time. OnChangePlugin before sync side effects. SmartPhrasePlugin before agent preview to avoid stale ranges.

Keyboard Shortcuts

ShortcutAction
Ctrl/Cmd + BBold
Ctrl/Cmd + IItalic
Ctrl/Cmd + UUnderline
Ctrl/Cmd + ZUndo
Ctrl/Cmd + Shift + ZRedo
TabAccept focused suggestion
EscReject focused suggestion
Ctrl/Cmd + Shift + AAccept all pending
Ctrl/Cmd + Shift + XReject all pending
Ctrl + ]Next anchor
Ctrl + [Previous anchor
Ctrl + EnterFill anchor and advance

Performance

  • Batch related changes in one editor.update(). Each call triggers reconciliation, so N separate updates cause N re-renders.
  • Memoize decorator output. DecoratorNode.decorate() runs on every re-render. Cache on the input data, not the render count.
  • Update overlays incrementally. When the suggestion overlay repositions, diff against the previous revision. Don’t walk the whole document.
  • Don’t call $getRoot().getTextContent() on keystroke. It concatenates the entire tree. Read from the specific node you need.
  • Debounce sync writes. Group rapid keystrokes into one transaction before sending. One PATCH per burst, not one per keypress.

Data Model

Rosetta stores documents as Lexical node trees, not HTML strings. The node tree is the canonical form. Everything else (pending edits, template anchors, sync payloads) references positions inside it.

Core Entities

  • Document nodes: block-level containers (paragraphs, headings) and inline marks (bold, italic, code).
  • Pending edits: agent-proposed changes that haven’t been applied. They live outside the document until the user accepts them.
  • Template anchors: named regions defined by a template. A locked anchor can’t be modified by any agent.

Pending Edit Shape

A pending edit is self-describing. It carries where it applies, both sides of the diff, the agent’s reasoning, and which agent produced it. That’s enough to render, audit, or re-rank the edit without touching the document.

interface PendingEdit { id: string; type: "insert" | "replace" | "delete"; start: number; end: number; original: string; replacement: string; reasoning: string; agent: string; }

For the full lifecycle (how a pending edit is remapped when the user types, how conflicts are handled, when an edit is marked stale) see the Editor Guide.

Keyboard Shortcuts

KeyAction
TabAccept current edit
EscReject current edit
Ctrl+Shift+AAccept all pending edits
Ctrl+Shift+XReject all pending edits

Notes

  • Shortcuts apply when the editor has focus.
  • Hover a pending edit to preview the diff before accepting.

Workflows

Rosetta puts note drafting, templates, and agent tools in one editor.

Document State

Document state is local-first. Every change is written to the device first and synced in the background.

  • Local writes first: keystrokes hit the local store before any network call, so typing is never blocked on the network.
  • Offline editing: you can keep writing without a connection. Queued changes are flushed when the device is back online.
  • Deterministic merge: when edits arrive from multiple devices, they’re ordered by operation timestamp and source. Every client ends up with the same document.

Templates

Templates define note structure and field behavior.

Interactive Fields

  • Text fields: {{chief_complaint}}
  • Select menus: {{SELECT:severity|mild,moderate,severe}}
  • Dates: Auto-formatted date pickers

Locked Anchors

Template regions that agents can’t write to: legal disclaimers, required section headers, billing codes. Only a user edit can change a locked anchor. This keeps compliance-sensitive content stable no matter how many agent runs a note goes through.

Agents

Run agents from Chat (Ctrl/Cmd + K) or by right-clicking selected text. Each run produces a pending suggestion. Nothing writes to the document until you accept it.

AgentWhat it does
ShorthanderExpands medical abbreviations (SOB becomes shortness of breath). Skips locked anchors.
IngesterParses uploaded PDFs and text files into searchable chunks. Keeps the original metadata so you can trace any chunk back to its source.
ReasonerGenerates ranked differentials and problem-based plans. Weighs claims that have retrieved evidence over ones that don’t.
FormatterRewrites selected text to match a requested format (SOAP, bullet list, table). The meaning stays the same.
CitationsSearches PubMed, Embase, and Google Scholar. Attaches references in the configured style. Vancouver is the default.

SmartPhrases

Custom text expansion shortcuts:

TriggerExpansion
.ccChief Complaint section
.hpiHistory of Present Illness
.rosReview of Systems
.pePhysical Examination
.apAssessment & Plan
.dcDischarge instructions

State Flow

Every note moves through the same four states from blank page to export. Transitions go forward in normal use. You can always go back to enrichment to run more agents, but export won’t run until validation passes.

Typical Stages

Validation Points

Export is blocked unless all three are true:

  • Every required template field has a value.
  • Locked anchors match the template source exactly.
  • Any claim that requires evidence has a citation attached.

Template Pipeline

A template defines both the structure of a note (sections, headers) and the rules it has to follow (required fields, locked regions). Every agent run goes through the same pipeline, which is how the rules get enforced.

Pipeline

  1. Select template: load the structure and its anchor/field definitions.
  2. Bind field values: resolve SELECT, MULTISELECT, and DYNAMIC_SELECT inputs and insert them at their anchors.
  3. Run agent transforms: agents propose edits against the bound document.
  4. Validate locked regions: any proposed change that overlaps a locked anchor is dropped here, before it reaches the review queue.
  5. Queue pending edits: what’s left goes into the review queue for you to accept or reject.

Guardrails

  • Agents can’t write to locked regions at any step.
  • Required fields are checked at export, not at drafting. You can fill sections in any order.

RAG

Every agent response in Rosetta is backed by retrieved sources: your uploads, your account library, and public medical databases. Agents don’t answer from memory alone.

Architecture

Source documents are split into passages, and each passage is converted into a vector that represents its meaning, not the exact words. A query is turned into a vector the same way. Retrieval returns the passages whose vectors are closest to the query vector (by cosine similarity). That’s why a search for “heart failure treatment” can match a passage that says “HFrEF management”: the two mean roughly the same thing, so their vectors sit near each other.

Semantic vs Keyword

Keyword Search (Traditional):

Query: "heart failure treatment" Matches: Exact text "heart failure" AND "treatment" Misses: "HFrEF management", "cardiac dysfunction therapy"

Semantic Search (Rosetta):

Query: "heart failure treatment" Matches: Any semantically similar concepts - "HFrEF management" - "cardiac dysfunction therapy" - "GDMT for reduced EF"

Source Types

  1. Local: sources attached to the current note. Good for one-off uploads tied to a specific patient.
  2. Account: guidelines, protocols, and papers in your persistent library. Indexed once and available to every note on your account.

PubMed Integration

PubMed is a third source type. Unlike local and account sources, it’s queried live:

  • The agent rewrites your request into a PubMed search string.
  • Top results come back with their abstracts and metadata.
  • Those abstracts are embedded and mixed into the same retrieval pool as your local and account sources. Citations can come from any of the three.

Usage Examples

Evidence-Based Treatment

Add a guideline PDF to your Account library. Ask “heart failure reduced ejection fraction treatment” in a note. The agent retrieves guidance and generates a plan with citations from the uploaded PDF.

Literature Review

The agent searches PubMed for recent abstracts on a topic and generates a summary with current scoring criteria.

Institutional Protocols

Upload a hospital protocol to the Account library. Query it by name (e.g., “UCSF sepsis bundle timing”) and the agent returns the relevant details.

Retrieval Pipeline

Before an agent generates a response, it first pulls supporting passages from your library and the literature. The model always sees the retrieved passages along with the question.

Flow

  1. Chunk: split source documents into passages. Each chunk is small enough to embed but long enough to carry meaning (200–500 tokens works well).
  2. Embed: turn each chunk into a vector that encodes what it means.
  3. Index: store the vectors with their metadata (source, date, author, scope).
  4. Retrieve: find the top-k chunks whose vectors are closest to the query vector.
  5. Re-rank: reorder those chunks with a cross-encoder, then pass the winners into the generation prompt as context.

Retrieval Controls

  • Source filters: limit retrieval to a specific library (local, account, public).
  • Date windows: limit to a time range, for example “guidelines from the last five years.”
  • Relevance thresholds: drop chunks below a minimum similarity score so weak matches don’t dilute the context.

Citations

When retrieval returns supporting passages, each one is attached to the response as a citation. If nothing was retrieved, the response comes back without citations. Rosetta won’t make one up.

Citation Payload

Each citation is self-contained. title is human-readable, url points at the document the agent retrieved, and relevance_score is the similarity score after re-ranking (0–1) so clients can sort or filter.

{ "title": "IDSA/ATS CAP Guidelines (2019)", "url": "https://www.idsociety.org/...", "relevance_score": 0.98 }

Best Practices

  • Prefer primary literature. Guidelines and peer-reviewed papers outrank summaries and review sites.
  • Use stable URLs. Link to DOIs or canonical publisher pages. Avoid search-result URLs that break when the session ends.
  • Show the score. Displaying relevance_score makes the difference between a 0.98 match and a 0.62 best-effort visible to the user.

API Reference

Rosetta provides a REST API for documents, templates, tool requests, and export workflows.

Base URL

https://philipshih.org/apps/rosetta/api/v1

Authentication

Bearer token in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Rate Limiting

TierLimitBurst
Free100 requests/hour10 concurrent
Professional1,000 requests/hour50 concurrent
Enterprise10,000 requests/hour100 concurrent

Documents API

List Documents

GET /v1/documents

Query Parameters

ParameterTypeDescription
limitintegerResults per page (default: 20, max: 100)
offsetintegerPagination offset (default: 0)
sortstringcreated_at, updated_at, title
orderstringasc or desc
searchstringFull-text search query
tagstringFilter by tag

Example

curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://philipshih.org/apps/rosetta/api/v1/documents?limit=10&sort=created_at&order=desc"

Get Document

GET /v1/documents/:id

Create Document

POST /v1/documents

Request Body

{ "title": "New Progress Note", "content": "Document content in markdown...", "tags": ["progress-note"], "template_id": "tpl_123", "metadata": { "document_type": "progress_note", "date_of_service": "2026-01-20" } }

Update Document

PATCH /v1/documents/:id

Delete Document

DELETE /v1/documents/:id

Delete Response

{ "success": true, "message": "Document moved to trash", "recoverable_until": "2026-02-19T09:15:00Z" }

Templates & Tools

Templates

List Templates

GET /v1/templates

Get Template

GET /v1/templates/:id

Tool Requests

Ask Question

POST /v1/ai/ask

Request Body

{ "question": "What is the treatment for community-acquired pneumonia?", "context": "Outpatient, no comorbidities", "include_citations": true }

Generate Text

POST /v1/ai/generate

Request Body

{ "prompt": "Expand on COPD exacerbation", "context": "Progress note, moderate severity", "max_tokens": 500, "temperature": 0.7 }

Export & Webhooks

Export Document

POST /v1/documents/:id/export

Request Body

{ "format": "pdf", "options": { "include_metadata": true, "include_citations": true, "page_size": "letter", "orientation": "portrait" } }

Supported Formats

  • pdf
  • docx
  • html
  • txt
  • markdown

Webhooks

Subscribe to account events from Settings -> Webhooks.

Available Events

  • document.created
  • document.updated
  • document.deleted
  • export.completed
  • ai.query_completed

Payload Example

{ "event": "document.created", "timestamp": "2026-01-20T10:30:00Z", "data": { "document_id": "doc_abc123", "title": "New Progress Note" } }

Errors & SDKs

Error Response Format

{ "error": { "type": "validation_error", "message": "Invalid request parameters", "details": [ { "field": "title", "issue": "Title is required" } ], "request_id": "req_abc123" } }

HTTP Status Codes

CodeMeaningDescription
200OKSuccessful request
201CreatedResource created successfully
400Bad RequestInvalid request parameters
401UnauthorizedMissing or invalid API key
403ForbiddenInsufficient permissions
404Not FoundResource does not exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorContact support

Error Types

  • authentication_error
  • validation_error
  • permission_error
  • rate_limit_error
  • not_found_error
  • server_error

SDKs

Python

pip install rosetta-client
from rosetta import RosettaClient client = RosettaClient(api_key="YOUR_API_KEY") documents = client.documents.list(limit=10)

JavaScript / TypeScript

npm install @rosetta/client
import { RosettaClient } from "@rosetta/client"; const client = new RosettaClient({ apiKey: "YOUR_API_KEY" }); const documents = await client.documents.list({ limit: 10 });
Last updated on