Skip to documentation
Documentation menu
On this page

AirPrompter documentation

Call a hosted environment with the OpenAI or Anthropic SDK

Point the provider SDK you already use at a hosted Team Agent environment, name the prompt as the model, and know what is accepted, ignored, and refused.

Available

An environment can run hosted: AirPrompter runs the promoted version on the organization’s model access and meters it, and there is no store, no model key, and no SDK of ours on your side. You call it with the OpenAI or Anthropic SDK you already use. Change the base URL and the key, name the prompt as the model, and nothing else at the call site changes.

Before you start

You need:

  • an environment whose sync mode is hosted, with a prompt promoted to it;
  • a run key for that environment from the agent’s Settings → Keys (a run key is a separate kind from an Agent key: it may execute and spend, and is never co-granted by default), stored as AIRPROMPTER_RUN_KEY;
  • the environment’s run URL, shown on the environment’s Settings; and
  • a prompt that declares exactly one end-user variable, which is where the user turn goes.

Point the SDK at the environment

SDKBase URLPath the SDK appends
openai (any language)https://<run-url>/v1/agents/<agentId>/openai/chat/completions, /responses
@anthropic-ai/sdk or anthropichttps://<run-url>/v1/agents/<agentId>/anthropic/v1/messages

The environment is the key’s own: a run key is bound to one environment, so the path carries no environment segment. The key goes where the SDK puts it, Authorization: Bearer for OpenAI or x-api-key for Anthropic; both reach the same check.

import OpenAI from "openai";

const openai = new OpenAI({
  baseURL: `https://${process.env.AIRPROMPTER_RUN_URL}/v1/agents/${process.env.AIRPROMPTER_AGENT_ID}/openai`,
  apiKey: process.env.AIRPROMPTER_RUN_KEY,
});

const completion = await openai.chat.completions.create({
  model: "slot:support.triage",                        // the prompt's key in code, never a model name
  messages: [{ role: "user", content: ticketText }],   // the one user turn: the prompt's end-user variable
  airprompter: { variables: { team: "Billing" } },     // the prompt's other declared variables
  user: "customer-42",                                 // sticky arm assignment during a rollout; hashed on arrival, never stored
} as any);

completion.choices[0].message.content;
(completion as any).airprompter.runRef;                // also in the X-AirPrompter-RunRef response header
import os
import anthropic

client = anthropic.Anthropic(
    base_url=f"https://{os.environ['AIRPROMPTER_RUN_URL']}/v1/agents/{os.environ['AIRPROMPTER_AGENT_ID']}/anthropic",
    api_key=os.environ["AIRPROMPTER_RUN_KEY"],
)

message = client.messages.create(
    model="slot:support.triage",
    max_tokens=400,                                     # required on Messages
    messages=[{"role": "user", "content": ticket_text}],
    metadata={"user_id": "customer-42"},                # sticky arm assignment
    extra_body={"airprompter": {"variables": {"team": "Billing"}}},
)

The airprompter extension is an extra body field. The OpenAI SDKs accept unknown fields at the top level in TypeScript with a cast; in Python pass it through extra_body=.

Expected result: the answer is the provider’s own shape with model echoed as slot:support.triage, an airprompter field carrying runId and runRef beside the standard fields, and the same runRef in the X-AirPrompter-RunRef header.

What the mapping does

Field in your callBecomesRule
modelThe slotMust be slot:<tag>; a real model name is refused model_not_a_slot
A system or developer message, instructions, or systemNothingRefused system_message_not_allowed: the sealed template is the system prompt
The one user turnThe prompt’s single end-user variableExactly one turn of text; a second turn, an assistant turn, an image, audio, or a tool result is unsupported_messages
airprompter.variablesThe other declared variablesUnknown names are render_unknown_variable; missing required ones render_missing_variable; an optional variable you leave out renders the default its prompt settings declare, if any
airprompter.subjectHash, else user (OpenAI) or metadata.user_id (Anthropic)The rollout subjectHashed on the way in and never stored
max_completion_tokens / max_tokens / max_output_tokensThe output capAbove the release’s ceiling is refused; max_tokens is required on Messages
streamServer-sent events in the provider’s own event shapeThe SDK’s stream() helpers assemble them
airprompter.idempotencyKey (or the Idempotency-Key header), airprompter.stepId, airprompter.metadataThe same as a direct hosted runThe same replay window, workflow step, and echoed metadata

Accepted and ignored, because the release owns them: temperature, top_p, stop / stop_sequences, top_k, presence_penalty, frequency_penalty, seed, logit_bias, store, stream_options, service_tier, reasoning_effort / reasoning, verbosity, parallel_tool_calls, prompt_cache_key, safety_identifier, logprobs, top_logprobs, truncation, include, text, thinking. The run uses the slot’s own inference settings, sealed with the version.

Refused by name (unsupported_parameter, the name in detail): tools, tool_choice, functions, function_call, response_format, audio, modalities, prediction, web_search_options, n above 1, previous_response_id, conversation, background, prompt, mcp_servers, container. Tool calling beyond what the release renders is out of scope; so is bringing your own model key.

Read the answer

Run outcomeChat CompletionsResponsesMessages
Finishedfinish_reason: stopstatus: completedstop_reason: end_turn
Hit the capfinish_reason: lengthstatus: incompletestop_reason: max_tokens
Usageprompt_tokens includes cached; prompt_tokens_details.cached_tokensinput_tokens includes cached; input_tokens_details.cached_tokensinput_tokens uncached; cache_read_input_tokens

Streams follow each provider’s event sequence; usage is always sent on the final event (Chat needs no stream_options.include_usage). A refusal after the stream began is the provider’s error event on the open stream.

Every refusal is the provider’s error shape with the status a direct hosted run would answer, plus the AirPrompter code beside it:

{ "error": { "message": "…", "type": "invalid_request_error", "param": null, "code": "model_not_a_slot" },
  "airprompter": { "code": "model_not_a_slot", "detail": "gpt-5" } }
CodeStatusMeaning
model_not_a_slot400model is not slot:<tag>
system_message_not_allowed400A system or developer message, instructions, or system
unsupported_messages400More than one turn, an assistant turn, a non-text part, a tool result
unsupported_parameter400A refused parameter, named in detail, or a malformed airprompter extension
no_end_user_variable400The prompt declares no single end-user variable for the user turn
max_tokens_required400Messages only
slot_not_found404No such prompt promoted to this environment
allowance_exhausted402The organization is out of AI credits; with auto refill on, Retry-After says when to try again
rate_limited429Retry after the Retry-After header
model_unavailable503The pinned model cannot be reached right now
meter_unavailable503Billing facts the meter needs could not be read; nothing was charged. Retry after the Retry-After header

Use feedback and the hosted client

The runRef the endpoint returns is the same token a direct hosted run mints. Keep it beside your own record and file feedback later from any process holding the run key, with the thin hosted client:

import { ManagedAgent } from "@airprompter/agent-sdk";

const hosted = await ManagedAgent.start({ agentId, target: "prod", apiKey: process.env.AIRPROMPTER_RUN_KEY!, baseUrl: runUrl });
await hosted.feedback(runRef, { accepted: true, rating: 4 });

ManagedAgent also runs prompts directly (run, stream, workflow) when you want typed refusals and the catalogue of slots, variables, and steps; in Python it is ManagedAgent.start(agent_id=, target=, api_key=, base_url=). Sources your app registers (ManagedAgent.start({ variables }); Python ManagedAgent.start(variables=...), plain callables only) fill the required declared variables, and any the prompt’s settings mark as filled by your app, before the run is posted — the hosted route has no way into your systems, so a source whose trust is stricter than the prompt’s declaration is refused rather than sent unfenced. The compatible endpoints exist for the call sites you do not want to change.

Apply it to day-to-day work

Move an existing OpenAI-based feature under governance without rewriting its call site:

  1. Keep the same client; swap the base URL and the key from configuration.
  2. Replace the model name with slot:<tag>.
  3. Delete the system prompt from the request, because the release now owns it, and move any operator-trust values into airprompter.variables.
  4. Store the runRef from the answer beside your own record.

What used to be a prompt change in your code is now a promotion on the board.

Troubleshooting

system_message_not_allowed

Remove the system or developer message (or instructions / system) from the call. The release’s template is the system prompt; put operator-trust values in airprompter.variables.

no_end_user_variable

The prompt declares zero or several end-user variables. Either declare exactly one, or pass every value through airprompter.variables and send an empty user turn.

unsupported_parameter naming tools

Tool calling through the compatible endpoint is out of scope. Run tools in your own code and call the endpoint for the prompt only, or run the prompt from your own runtime with the TypeScript or Python SDK.

The temperature you sent had no effect

That is by design: the release’s sealed settings are used and the caller’s are named on the run’s log line as ignored.

Completion checklist

  • The run key reaches the process only through the environment, and it is a run key, not an Agent key.
  • model is slot:<tag> and no system message is in the call.
  • The answer carries airprompter.runRef and you store it beside your record.
  • Your error handling reads airprompter.code beside the provider’s error shape.
  • Rollout stickiness passes user or metadata.user_id, never a raw identifier you need to keep private.

Next steps

If a feature outgrows one user turn, or needs tools, move it to your own runtime with the resident TypeScript or Python integration; the prompt’s key in code is the same.

Your Privacy Choices

We use advertising cookies and share limited information, such as a hashed email address when you sign up or subscribe, with advertising partners like Meta and Google to measure our ads. Some US state laws treat this as a "sale" or "sharing" of personal information.

Advertising cookies and sharing
On: ad partners can measure visits and sign-ups from their ads. Off: no ad tags load and we send nothing to ad partners.
Read our Privacy Policy