TypeScript application example
Retrieve a published ticket router and execute it through OpenRouter.
Retrieve the published ticket router, append a user input, and call OpenRouter. This is a complete server-side example using raw HTTP. It reports the prompt version and validates the returned team label before your application uses it.
Prepare the prompt and environment
You need Node.js 24 or later. No SDK or package installation is required. Complete the ticket-routing quickstart, select an OpenRouter model route, and publish the version you want to use. The example expects User message input mode and no output schema.
Create a retrieval key and provide a separate OpenRouter API key for the application's model call. The provider bills this call to that key; it does not use PromptLens Credits or a provider secret stored in your organization.
export PROMPTLENS_RETRIEVAL_URL='https://www.promptlens.io'
export PROMPTLENS_PROMPT_ID='YOUR_PROMPT_ID'
export PROMPTLENS_RETRIEVAL_KEY='YOUR_RETRIEVAL_KEY'
export OPENROUTER_API_KEY='YOUR_OPENROUTER_API_KEY'Use https://www.promptlens.io as the API origin. Keep the retrieval and provider keys in your server environment; do not expose them through browser code.
Run the example
Download route-ticket.ts, or save the complete code below under that filename:
function requiredEnv(name: string) {
const value = process.env[name];
if (!value) throw new Error(`Set ${name} before running this example.`);
return value;
}
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
async function requestJson(url: string, key: string, body?: unknown) {
const response = await fetch(url, {
method: body === undefined ? "GET" : "POST",
headers: {
Authorization: `Bearer ${key}`,
...(body === undefined ? {} : { "Content-Type": "application/json" }),
},
body: body === undefined ? undefined : JSON.stringify(body),
redirect: "error",
signal: AbortSignal.timeout(60_000),
});
if (!response.ok) throw new Error(`Request failed: HTTP ${response.status}.`);
const data: unknown = await response.json();
if (!isObject(data)) throw new Error("Expected a JSON object response.");
return data;
}
async function main() {
const baseUrl = requiredEnv("PROMPTLENS_RETRIEVAL_URL").replace(/\/$/, "");
const promptId = requiredEnv("PROMPTLENS_PROMPT_ID");
const retrievalKey = requiredEnv("PROMPTLENS_RETRIEVAL_KEY");
const providerKey = requiredEnv("OPENROUTER_API_KEY");
const ticket = process.argv.slice(2).join(" ").trim();
if (!ticket) throw new Error('Usage: node route-ticket.ts "Your ticket"');
const prompt = await requestJson(
`${baseUrl}/api/v1/prompts/${encodeURIComponent(promptId)}`,
retrievalKey,
);
if (
prompt.inputType !== "user_message" ||
prompt.outputSchema !== undefined
) {
throw new Error(
"This example needs a User message prompt without an output schema.",
);
}
if (
typeof prompt.modelRouteId !== "string" ||
!prompt.modelRouteId.startsWith("openrouter:")
) {
throw new Error(
"Select and publish an OpenRouter model route for this example.",
);
}
if (!Array.isArray(prompt.messages) || prompt.messages.length === 0) {
throw new Error("The saved prompt has no messages.");
}
const messages = prompt.messages.map((message: unknown) => {
if (
!isObject(message) ||
!["system", "user", "assistant"].includes(String(message.role)) ||
typeof message.content !== "string"
) {
throw new Error("Invalid saved message.");
}
return { role: String(message.role), content: message.content };
});
const result = await requestJson(
"https://openrouter.ai/api/v1/chat/completions",
providerKey,
{
model: prompt.modelRouteId.slice("openrouter:".length),
messages: [...messages, { role: "user", content: ticket }],
...(typeof prompt.temperature === "number"
? { temperature: prompt.temperature }
: {}),
},
);
const choice: unknown = Array.isArray(result.choices)
? result.choices[0]
: undefined;
const message = isObject(choice) ? choice.message : undefined;
const output = isObject(message) ? message.content : undefined;
if (typeof output !== "string")
throw new Error("The provider returned no text answer.");
const team = output.trim();
if (!["billing", "technical", "account"].includes(team)) {
throw new Error("The model did not return an allowed routing label.");
}
console.log(JSON.stringify({ promptVersion: prompt.version, team }));
}
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : "Request failed.");
process.exitCode = 1;
});
export {};Run it with a ticket:
node route-ticket.ts "I was charged twice for my subscription."An illustrative successful result is {"promptVersion":1,"team":"billing"}. The version reflects your production label and the answer comes from your model call; it is not a guaranteed score or recorded test result.
How it works
The first request retrieves production because it has no selector. The script preserves the saved messages and temperature, appends the ticket, and maps the openrouter: route prefix to OpenRouter's model identifier. The second request uses OpenRouter's chat completion API.
The example rejects another input mode, an output schema, a non-OpenRouter route, and unexpected routing labels. When adapting it to another task, implement that task's input assembly and output validation explicitly. See input modes and structured output.
It makes a fresh retrieval on each invocation, so subsequent runs observe completed production label moves. For a long-running application, add ETag revalidation. HTTP errors and timeouts exit with a nonzero status; investigate their cause before retrying model calls that may already have incurred usage.