Skip to main content

BuddyPro Owner API — OpenAI-Compatible V1 Endpoint

version: 1.4 (2026-09-18)

OpenAI-compatible access to your BuddyPro instance, designed for owner and team member integrations — connecting your services, automations, and internal tools to BuddyPro. This endpoint accepts requests structured like the OpenAI Chat Completions API and returns responses in the same format, with BuddyPro-specific extensions in message (e.g., image, audio).

BuddyPro End-user API available

A separate API is available for end-users, letting them generate their own API keys that work directly with their personal BuddyPro profiles. See the End-user API documentation for details.

Overview​

PropertyValue
PathPOST /v1/chat/completions
AuthAuthorization: Bearer bapi_... header
FormatOpenAI Chat Completions compatible
StreamingNot supported yet

Persistent Memory & Conversation History​

Unlike stateless LLM APIs, BuddyPro maintains long-term memory and full conversation history for the profile tied to each API key. Every API request is treated exactly like a message sent in Telegram — it is saved to the profile's chat history, contributes to BuddyPro's memory about that user, and influences future responses.

This means:

  • Conversations are cumulative. BuddyPro remembers everything said through the API, just as it remembers Telegram conversations. You do not need to (and should not) send conversation history — just send the current message.
  • Memory builds over time. BuddyPro learns preferences, facts, and context from API interactions, the same way it does from Telegram chats.
  • Stateless mode available. Set x_buddy_saveToHistory: false to make a request that doesn't persist anything — no chat history, no memory updates, no profile changes. See Stateless Mode.
warning

Do not send conversation history in the messages array. Send only the current user message. BuddyPro stores and manages conversation context server-side.

Privacy & Data Access​

danger

The Owner API should NOT be used to create profiles for real people using test users. People expect private conversations and often share private or sensitive information. It is not a privacy-safe consumer-facing solution. The BuddyPro Owner API does not protect end-user data from the instance owner. The owner can access conversation history and memories of test accounts.

All API keys in the Owner API are generated by the BuddyPro instance owner or a team member. The owner has full access to any testing profiles created on their instance — they can switch to any test account via /test in Telegram, read its conversation history, and see everything BuddyPro remembers about that profile.

What this means in practice:

Don't build an integration with profiles for end-users on the BuddyPro Owner API, where users would chat with BuddyPro through your API key (e.g., a website chatbot), and each user would get a separate test profile — the BuddyPro instance owner could:

  • Switch to any of those test profiles
  • Ask BuddyPro: "Tell me everything you know about me"
  • Read the full conversation history and all stored memories for that profile

The owner knows the API key and the user identifiers, so there is nothing preventing them from accessing any profile created as a test user.

  • Internal automations and agents (e.g., CI/CD bots, monitoring alerts, internal tools)
  • Service-to-service integrations where the profile owner is also the API consumer
  • Prototyping and development with test users
  • Using test users in your integrations to avoid your integrations influencing your personal message history and memory
  • Scenarios where all API users are the same organization and aware of data visibility
  • Stateless Q&A (x_buddy_saveToHistory: false) — safe for any use case since nothing persists

What NOT to use the Owner API for​

  • Building consumer-facing products with separate profiles for real end-users created as test users
  • Creating profiles for real people using test accounts
  • Any scenario where end-users expect their conversations to be private from the instance owner

BuddyPro End-user API (Client API)​

The BuddyPro End-user API (Client API) is now available, allowing end-users to generate their own API keys that work directly with their personal BuddyPro profile. In the End-user API model:

  • The API key is generated by and known only to the end-user, not the instance owner
  • The instance owner cannot access the user's conversation history or switch to the user's profile through the bot
  • End-users have full data ownership over their profile

See the BuddyPro End-user API documentation for details and setup instructions.

Do not use the Owner API as a substitute for privacy-safe consumer access. For use cases needing privacy now, use x_buddy_saveToHistory: false (stateless mode) — nothing is stored, so there is nothing for the owner to access.

Reselling the End-user API to Your End-Users​

As a BuddyPro instance owner, you can resell End-user API access to your own end-users. Your users generate their own End-user API keys and buy prepaid credits to call the API — the money lands on your connected Stripe account, and you set your own markup on top of what BuddyPro charges you.

How billing works (two independent ledgers)​

LedgerWhat it isWhere the money goesCharged per request
End-user credit walletPrepaid balance your end-user tops upYour connected Stripe account (Connect direct charge)Your price = (what BuddyPro charges you) × (1 + your markup)
Your BuddyPro creditsBuddyPro billing you for the usageBuddyProWhat BuddyPro charges you for the request

Your profit is the difference between the two. The end-user's wallet is an accounting ledger on our side; the real money for it already sits on your Stripe account (it arrived there when the user topped up).

Prerequisites​

  • Connected Stripe account. Connect your Stripe account via Stripe OAuth — the same connection used to sell subscriptions to your instance. End-user credit checkouts are created as direct charges on this account.
  • Active subscription for the end-user. Your end-users must have an active subscription to your instance before they can set up End-user API credits.
  • USD only

Owner Commands​

These commands are available to the instance owner or a team member only.

CommandDescription
/setApiClientMarkup:{percent}Set your markup (%) added on top of what BuddyPro charges you, applied to your end-users' End-user API usage. Allowed range: 0–500%. If you don't set one, a default markup of 30% applies.
/setApiPaymentUrls:{successUrl} [cancelUrl]Set the redirect URLs shown after your end-users complete or cancel a credit checkout. Both must be valid https:// links; cancelUrl is optional. Use /setApiPaymentUrls:default to reset to the default pages.

Markup example: if BuddyPro charges you $0.13 for a request and you set /setApiClientMarkup:54, your end-user's wallet is billed $0.13 × 1.54 ≈ $0.20, and you keep the $0.07 difference.

Keep your BuddyPro credits funded​

BuddyPro bills you from your BuddyPro credits for every End-user API request your end-users make. If your BuddyPro credits run out, your end-users' requests fail with HTTP 402 owner_billing_unavailable until your balance is restored. Keep auto-recharge enabled on your BuddyPro credits so end-user traffic is never interrupted.

Authentication​

Getting an API Key​

info

By default, API conversations use the profile that generated the API key — messages are saved to its conversation history and contribute to its memory, even though they don't appear in Telegram. Use /test in Telegram before generating the key to bind it to a test account instead of your personal profile, so API interactions don't mix with your personal chat history. Your real owner or team member profile also has management commands that agents could misuse; issuing the key with user rights (see Key rights) removes access to them, and so does sending a user value. See User Isolation.

Send this command to your BuddyPro bot in Telegram, preferably on a testing account:

/generateApiKey:my-app

You'll receive a key starting with bapi_.

Key rights​

A key can be issued at one of two rights levels, given as the second argument:

LevelWhat the key can do
full (default)Everything the profile it runs on can do, including your owner/team management commands
userPlain-user rights only — owner/team management commands are refused with permission_denied
/generateApiKey:my-app:user

Issue any key you hand to a third party with user rights.

What user does not change: prompt customization stays available (x_buddy_systemPrompt, x_buddy_systemPromptMode, x_buddy_rolePrompt — a documented Owner API feature at both levels), usage is still billed to you, and the key still runs on your profile unless the request carries a user value. On that profile it keeps everything a plain user can do — reading its conversation history and memory, /getApiStats, and the API credit commands. Whether a request carries user is up to whoever sends it, so if the integrator must not reach your profile at all, generate the key from a /test profile (see the note above).

End-user API keys (/generateClientApiKey) are always plain-user — they cannot be issued any other way.

API Key Management​

CommandDescription
/generateApiKey:{name}Create a new API key with full rights
/generateApiKey:{name}:userCreate a new API key capped at plain-user rights
/invalidateApiKey:{name}Revoke a key by name or raw key
/getApiStatsList all active keys, their rights level and usage

Authenticating Requests​

Pass your API key via the Authorization header:

Authorization: Bearer bapi_xxxxxxxxxxxx

Endpoint​

POST https://api.buddypro.ai/v1/chat/completions
Authorization: Bearer bapi_xxxxxxxxxxxx
Content-Type: application/json

Request​

Content Input​

Standard OpenAI messages array. BuddyPro extracts the last user message for processing.

warning

Only one user message is allowed. BuddyPro manages conversation history server-side — do not send conversation turns. System and assistant messages are ignored.

Text only (string content):

{
"messages": [
{ "role": "user", "content": "Hello, how are you?" }
]
}

Multimodal (array content):

{
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "Describe this image" },
{ "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }
]
}
]
}

Content Part Types​

When using array content inside messages[].content:

Text​

{ "type": "text", "text": "What is the weather today?" }

Max 50,000 characters per text part.

Image (image_url)​

{ "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }

Supported URL types:

  • HTTPS URL — must be publicly accessible
  • Data URI — data:image/png;base64,iVBORw0KGgo...

Max 5 images per request. Max 40 MB per remote download.

Audio Input (input_audio)​

{
"type": "input_audio",
"input_audio": {
"data": "<base64>",
"format": "mp3"
}
}

Fields:

FieldTypeDescription
datastringBase64-encoded audio data or URL
formatstringAudio format: mp3, wav, ogg, aac, flac
type"url" | "base64"Optional data type hint. Default: base64

Audio via URL:

{
"type": "input_audio",
"input_audio": {
"data": "https://example.com/audio.mp3",
"type": "url",
"format": "mp3"
}
}

Document / File (file)​

Attach a document (PDF, Office file, text, etc.) as an OpenAI-style file content part. BuddyPro extracts the document to text server-side and uses it as context — exactly like sending a document to the bot on Telegram or mobile. The model does not receive the raw file bytes, so the assistant's reply is a normal text (or media) response — document inputs are not echoed back.

{
"type": "file",
"file": {
"filename": "report.pdf",
"file_data": "data:application/pdf;base64,<base64>"
}
}

Fields:

FieldTypeDescription
filenamestringSuggested file name (used to infer type when the MIME is ambiguous)
file_datastringData URI: data:<mime>;base64,<base64>. Mutually exclusive with file.url
urlstringBuddyPro extension — an http(s) URL to a hosted file to download instead of inline file_data. The document type comes from the file bytes (PDF, Office files) or the server's Content-Type; a generic Content-Type (application/octet-stream, text/plain) is refined by the filename / URL extension when the bytes are consistent with it, otherwise it is kept

Document via URL (BuddyPro extension):

{
"type": "file",
"file": {
"filename": "report.pdf",
"url": "https://example.com/report.pdf"
}
}
note

file.file_id is not supported — BuddyPro has no Files API. Provide the document inline via file_data or hosted via url.

Audio is not sent as a file part — use the separate input_audio part for audio.

Supported document MIME types: application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document (docx), application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (xlsx), application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.presentation (pptx), text/plain, text/csv, text/html, text/markdown, application/json, application/xml, text/xml, and the video types video/mp4, video/quicktime, video/webm, video/x-msvideo, video/mpeg (transcribed to text). application/octet-stream is accepted as an unknown-type fallback. Unsupported MIME types return error code invalid_media_type.

Max 5 documents per request. Max 40 MB per document (URL-fetched or decoded base64).

Large files

Prefer file.url over inline file_data. Inline base64 rides the request body, which is capped at ~6 MB (~4 MB of actual file) well before the 40 MB validation cap — this applies to all inline media, not just documents. See Media Limits.


Audio Output (TTS via Modalities)​

To request TTS audio output, use the OpenAI-style modalities and audio fields:

{
"modalities": ["text", "audio"],
"audio": { "format": "mp3" },
"messages": [
{
"role": "user",
"content": [
{ "type": "input_audio", "input_audio": { "data": "<base64>", "format": "mp3" } }
]
}
]
}
  • When modalities includes "audio", TTS is enabled
  • audio.format defaults to "mp3" if omitted
  • TTS only applies when audio input is present in the request
  • audio.voice is accepted but ignored — voice is set by the bot owner

Request Fields Reference​

FieldTypeRequiredDescription
messagesarrayYesOpenAI-format messages. Must contain exactly 1 user message.
modalities["text"] | ["text", "audio"]—Output types. Include "audio" to enable TTS
audioobject—Audio config: { "format": "mp3"|"wav" }
userstring—Custom user identifier for an isolated test profile. When omitted, requests use the key's profile. See User Isolation
x_buddy_saveToHistoryboolean—When false, nothing is saved to chat history, memory, or profile. Default: true. See Stateless Mode
x_buddy_systemPromptstring—Custom system prompt text (max 50,000 chars). See Custom System Prompt
x_buddy_systemPromptModestring—System prompt mode: "replace" or "add". Default: "add"
x_buddy_rolePromptstring—Custom role prompt override (max 50,000 chars). When set, skips role prompt lookup/creation and uses this directly. See Custom Role Prompt
x_buddy_commandResultModestring—How slash-command results are returned: "text" (default, natural-language) or "deterministic" (structured object, no LLM). B2B API keys only. See Deterministic Command Results

Client Request ID​

Provide via the X-Client-Request-Id HTTP header:

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "X-Client-Request-Id: my-app_req-42" \
-d '{
"messages": [
{ "role": "user", "content": "Hello!" }
]
}'

Max 64 characters, alphanumeric + hyphens + underscores only.

User Isolation​

By default, API requests use the profile that generated the API key. Conversations are stored in that profile's message history and contribute to its memory. If the key was generated after running /test in Telegram, it is bound to that test account — not to the owner's personal profile.

To create isolated test profiles, use the user field. Each unique user value creates a fully separate profile with its own chat history, long-term memory, and settings.

Modes:

ModeHow to activateBehavior
Key profile (default)Omit userUses the profile that generated the API key (message history, settings, memory)
Named userSet user to a custom identifierCreates a separate isolated profile per user value — useful for creating specialized profiles with specific memory or chat history
StatelessSet x_buddy_saveToHistory: falseNothing is persisted. Can be combined with either mode above

Named user example:

{
"user": "test-user-joe",
"messages": [
{ "role": "user", "content": "Hello!" }
]
}

Each unique user value creates a fully isolated conversation context with its own:

Use this when you want to simulate multiple user profiles with different data or use cases through a single API key.

warning

Named test user profiles are not private from the API key owner. The owner can access any test profile via /test in Telegram. Do not use the user field to create profiles for real people with the expectation of privacy. For privacy-safe end-user access, use the BuddyPro End-user API instead. See Privacy & Data Access for details.

What a user value can and cannot do​

Isolation is also a permission boundary. A request that names a user drops the instance owner's and team's rights, so instance-management commands (/createPro, /setupFapi, /disableUser, …) return permission_denied. Only the key holder's own profile (no user) keeps them.

So sending a user value is what stops an agent holding your key from managing your instance.

Validation rules for user:

  • Alphanumeric characters, hyphens, underscores, and dots only (a-z, A-Z, 0-9, -, _, .)
  • Cannot be a purely numeric value (e.g., 123456789) — to prevent conflict with real user IDs
  • Max 128 characters
  • No spaces

Stateless Mode​

Set x_buddy_saveToHistory: false to make a request that does not persist anything. In stateless mode:

  • Nothing is saved to chat history
  • No updates to Pinecone long-term memory
  • No profile updates or preference learning
  • The AI still responds normally using existing context

This is useful for:

  • Pure Q&A interactions where you don't want to pollute the profile's history
  • Privacy-sensitive use cases where you need to ensure nothing is stored
  • Testing and prototyping without affecting the profile

Stateless mode can be combined with any isolation mode (default, named user, or owner profile).

Example — stateless Q&A:

{
"x_buddy_saveToHistory": false,
"messages": [
{ "role": "user", "content": "What is the best way to build a profitable sales funnel?" }
]
}

Example — stateless with named user:

{
"user": "test-user-joe",
"x_buddy_saveToHistory": false,
"messages": [
{ "role": "user", "content": "Based on what you know about me, write me 10 ideas to improve my business." }
]
}

Custom System Prompt​

Override or extend your BuddyPro system prompt per request using x_buddy_systemPrompt and x_buddy_systemPromptMode.

ModeBehavior
"replace"Completely replaces your system prompt
"add" (default)Appends to the existing system prompt

Replace mode — custom persona:

{
"x_buddy_systemPrompt": "You are now an email writing assistant. Your response must always be an email draft based on the user's request and your know-how. Do not include any explanations or additional text.",
"x_buddy_systemPromptMode": "replace",
"messages": [
{ "role": "user", "content": "Write an email about 50% sale on the Business Mastery program." }
]
}

Add mode — extend existing prompt:

{
"x_buddy_systemPrompt": "IMPORTANT: Write your next response strictly in Spanish. No comments, just answer.",
"x_buddy_systemPromptMode": "add",
"messages": [
{ "role": "user", "content": "How would you write a message to invite people to join my new program?" }
]
}

Validation rules for x_buddy_systemPrompt:

  • Max 50,000 characters
  • Script tags (<script>...</script>) are stripped
  • Control characters are stripped (newlines, tabs, and carriage returns are preserved)
note

If x_buddy_systemPromptMode is omitted, it defaults to "add".


Custom Role Prompt​

Override the AI's role prompt per request using x_buddy_rolePrompt. When this field is set, BuddyPro skips the role prompt lookup/creation step and uses the provided text directly as the role definition.

How it works: Normally, BuddyPro analyzes each message to select an appropriate "role" (e.g., coding assistant, creative writer, advisor) and fetches or generates a role-specific prompt. With x_buddy_rolePrompt, you provide that role prompt yourself, giving you full control over the AI's persona for that request. Feature detection (web search, reminders, music generation, etc.) still runs normally.

Example:

{
"x_buddy_rolePrompt": "You are a minimalist poet who specializes in technical haiku. Keep responses to exactly 3 lines in 5-7-5 syllable format.",
"messages": [
{ "role": "user", "content": "Write a haiku about code" }
]
}

Validation rules for x_buddy_rolePrompt:

  • Max 50,000 characters
  • Script tags (<script>...</script>) are stripped
  • Control characters are stripped (newlines, tabs, and carriage returns are preserved)
  • Empty strings (after sanitization) are ignored - normal role detection runs

Combining with x_buddy_systemPrompt: Both can be used together. The system prompt controls the base personality/instructions, while the role prompt adds a specific role overlay. They are injected at different points in the prompt chain.

Deterministic Command Results​

Availability

"deterministic" mode is available for B2B API keys only. Requests with a non-B2B API key that set x_buddy_commandResultMode: "deterministic" are rejected with 403 insufficient_permissions. The default "text" mode is available to all API types.

By default, sending a slash command (e.g. /setVoice:alloy) returns a natural-language response — the command's outcome is rephrased by the AI, so the wording varies between calls. Set x_buddy_commandResultMode: "deterministic" to skip the AI entirely and receive a structured, machine-readable result instead:

{
"messages": [
{ "role": "user", "content": "/setVoice:alloy" }
],
"x_buddy_commandResultMode": "deterministic"
}

Response — content is usually null (it can carry text when a command additionally emits messages that are not its result) and the result is in message.x_buddy_commandResult:

{
"id": "chatcmpl-req_abc123",
"object": "chat.completion",
"created": 1710964800,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"x_buddy_commandResult": {
"command": "setVoice",
"status": "success",
"message": "Voice set to alloy."
}
},
"finish_reason": "stop"
}
]
}

x_buddy_commandResult fields:

FieldTypeDescription
commandstringThe command that was executed, without the leading slash (e.g. "setVoice")
statusstring"success" | "warning" | "error" | "in_progress" | "awaiting_input"
errorCodestringPresent only when status is "error" or "warning" (and optional even then — some cases have no matching code and carry the reason in message). One of: invalid_format, invalid_value, permission_denied, unknown_command, not_allowed_via_api, precondition_failed, client_api_disabled, payments_not_configured, subscription_required, not_found, internal_error, partial_failure
paramstringPresent only when exactly one command parameter is at fault — its name, matching the parameter names in the command's own :{…} syntax (e.g. "topUpAmount"). Complements errorCode, never replaces it: errorCode says what kind of problem it is, param says which input caused it. Absent when the result is not about a specific input (a failed precondition, an internal error)
messagestringThe raw command result text, verbatim (not AI-rephrased). Multi-message commands concatenate their messages with newlines

A rejected input, with param pointing at it (/setupApiCredits:5:2 below the minimum top-up):

"x_buddy_commandResult": {
"command": "setupApiCredits",
"status": "error",
"errorCode": "invalid_value",
"param": "topUpAmount",
"message": "Error: Minimum topUpAmount is $10."
}

Status semantics:

  • success — the command completed. Informational commands (lists, stats) also return success with the payload in message.
  • warning — the command completed, but with a caveat you should know about; nothing is left for you to do. The dividing line between warning and error is exactly that: if the outcome requires you to act (re-run the command, fix something by hand), it is an error. It is produced whenever the outcome is not the plain success you asked for. Most often the requested state was already in place, so the command changed nothing (errorCode: "precondition_failed") — /disableLicense on a license already disabled, /enableLicense on an enabled one, /enableMobileApp on an instance where the app is already on, /enableMessageAllUsers and /addSystemMessageRecipient for someone who already has it, /connectWeb on a bot already linked to the dashboard, /allowSharedStripe where sharing is already enabled, /unassignSubscription on an unassigned subscription. Or the command finished with nothing to report, or an optional side effect skipped or unverified (no errorCode — message says which): /enableMobileApp called without an e-mail address, /setApplePackage when the App Store Connect check cannot run, /myLicense on a bot that has no license associated. Neither list is exhaustive. A command that rejects your input is not this case and stays an error — /createPro with an already-used license, for instance, needs a different license from you. Treat warning as a completed command whose message is worth surfacing to a human.
  • error — the command failed synchronously; errorCode gives the machine-readable reason. A run that finished but had some of its items fail (e.g. /update with files that failed to transcribe, /updateRoles with roles that failed to update) returns error with errorCode: "partial_failure". That code means the run finished with part of its work failed: message names the part that failed, and the update commands additionally list the failed items in the response content. Other commands produce it too — /enableLicense when the license was enabled but its pro account was not, /enableMobileApp when the app was switched on but the welcome e-mail could not be sent. An update that was paused before finishing (it hit the cost limit or timed out too many times) is also an error (errorCode: "precondition_failed"): nothing is running any more, so re-run the command to continue — do not poll for it.
  • in_progress — the work is not finished yet and is still running: it continues in a background run (e.g. /update knowledge sync handing off to its next iteration), or another update is already running — the message says which. Poll /updateStatus (deterministic: in_progress while a run is active, success with the last-successful-update timestamp when idle) to detect completion.
  • Stopping an update: /stopUpdate returns success in both cases — it registers a stop request; the message says whether a run was actually active. A running update stops at its next iteration boundary (up to several minutes), so poll /updateStatus until it reports idle rather than assuming the stop is immediate. If nothing was running, the stale stop request is cleared automatically when the next update starts — it will not block it.
  • awaiting_input — the command is waiting for your next message. /createVoiceClone is the only command that returns it, and the audio upload it waits for cannot be sent over the API (see the notes below); sending anything else cancels the flow.

Notes:

  • The mode only affects requests whose message is a slash command. Normal messages behave exactly as without the parameter (AI response, no x_buddy_commandResult field).
  • Onboarding/account flows (/start, /upgrade, /clear) and account gates (e.g. insufficient credits) are not regular commands — their normal replies are returned as plain content without x_buddy_commandResult. Exception: when such a flow fails before command dispatch with a pre-set error result (e.g. /clear failing to create the re-onboarding invite), that error is delivered as a structured x_buddy_commandResult (status: "error"), not as plain content.
  • The command runs the same way whoever sends it; what differs is what the exchange leaves behind. The raw result message is saved to chat history as the assistant's reply, following the same per-command storage rules as the Telegram chat (e.g. /help and /del are never stored). Whether the caller's own /command message is saved alongside it follows from two rules. First, a request that names a user is treated as an ordinary end user, without the instance owner's or team's rights — only a request on the key holder's own profile (no user field) keeps them. Second, an ordinary end user's /command message is not stored, on the API or in the Telegram chat; only Buddy's reply is. So a request with user leaves the result alone in history, and one without leaves both rows. x_buddy_saveToHistory: false still keeps the request fully stateless. Secret command parameters (private keys, API keys, tokens, license keys — e.g. /setupFapi, /setApplePrivateKey) are masked to *** before the message is stored; an exchange whose result carries a secret (e.g. the /myLicense key) is not stored at all. The audio-upload step of /createVoiceClone is not supported over the API: the command itself returns awaiting_input, but the upload that completes it has to be done in Telegram.
  • With the default "text" mode (or when the parameter is omitted) nothing changes — command results are returned as natural-language text.
  • Unknown commands return status: "error" with errorCode: "unknown_command"; commands you lack permission for return errorCode: "permission_denied".
  • /setupApiCredits distinguishes its three setup gates by errorCode, so you never have to read the message to know who has to act: client_api_disabled (the instance owner has not enabled the End-user API), payments_not_configured (the owner has no connected Stripe account to receive the payment), subscription_required (the caller has no active subscription to the instance). All three are status: "error" — each leaves something to be done. They carry no param: the amounts you sent are fine, the account state is not.
  • The credit commands (/setupCredits, /changeCreditsTopUp, /setupApiCredits, /changeApiCreditsTopUp) report param on every amount rejection, so invalid_value alone is never ambiguous — you learn whether topUpAmount or rechargeAt was refused. The published minimums/maximums plus the value you sent tell you which bound it was. When both amounts are malformed, only the first one is reported.

Response​

Response Headers​

HeaderDescription
x-request-idServer-generated unique request ID (always present)
x-client-request-idClient-supplied request ID echoed back (present only if provided)
Content-Typeapplication/json

Success — Text Only​

{
"id": "chatcmpl-req_abc123def456",
"object": "chat.completion",
"created": 1710964800,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! I'm doing well. How can I help you?"
},
"finish_reason": "stop"
}
]
}

Success — Image Output​

When BuddyPro generates an image, it appears in message.image:

{
"id": "chatcmpl-req_def456",
"object": "chat.completion",
"created": 1710964800,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Here's the image you requested:",
"image": {
"id": "image_req_def456_generated_image.png",
"data": "iVBORw0KGgo...",
"media_type": "image/png",
"file_name": "generated_image.png",
"caption": "A sunset over the ocean"
}
},
"finish_reason": "stop"
}
]
}

Success — Multiple Images​

When a response contains more than one image, image holds the first (for OpenAI SDK compatibility) and the complete ordered set is additionally provided in x_buddy_images:

{
"id": "chatcmpl-req_multi123",
"object": "chat.completion",
"created": 1710964800,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Here are the images you requested:",
"image": {
"id": "image-1-req_multi123-first.png",
"data": "iVBORw0KGgo...",
"media_type": "image/png",
"file_name": "first.png",
"caption": "A sunrise over the mountains"
},
"x_buddy_images": [
{
"id": "image-1-req_multi123-first.png",
"data": "iVBORw0KGgo...",
"media_type": "image/png",
"file_name": "first.png",
"caption": "A sunrise over the mountains"
},
{
"id": "image-2-req_multi123-second.png",
"data": "iVBORw0KGgo...",
"media_type": "image/png",
"file_name": "second.png",
"caption": "A sunset over the ocean"
}
]
},
"finish_reason": "stop"
}
]
}

The same pattern applies to audio: multiple audio items populate x_buddy_audios (with x_buddy_audios[0] equal to audio).

Success — Audio Output (Music / Meditation)​

Audio from music or meditation features is always returned regardless of modalities:

{
"id": "chatcmpl-req_ghi789",
"object": "chat.completion",
"created": 1710964800,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"audio": {
"id": "audio_req_ghi789_response.ogg",
"data": "<base64>",
"format": "ogg",
"transcript": null,
"media_type": "audio/ogg",
"file_name": "response.ogg"
}
},
"finish_reason": "stop"
}
]
}

Success — Text + TTS Audio​

When TTS is enabled via modalities and audio input was sent:

{
"id": "chatcmpl-req_abc123",
"object": "chat.completion",
"created": 1710964800,
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Why don't scientists trust atoms? Because they make up everything!",
"audio": {
"id": "audio_req_abc123_response.mp3",
"data": "<base64>",
"format": "mp3",
"transcript": "Why don't scientists trust atoms? Because they make up everything!",
"media_type": "audio/mpeg",
"file_name": "response.mp3"
}
},
"finish_reason": "stop"
}
]
}
note

When audio output is present, transcript contains the same text as content (the concatenated text response).

Response Fields Reference​

Top-level​

FieldTypeDescription
idstringUnique completion ID: chatcmpl-{requestId}
objectstringAlways "chat.completion"
creatednumberUnix timestamp (seconds) of request start
choicesarrayArray with single choice (index 0)
note

Request ID and processing time metadata are available via response headers (X-Request-Id, X-Client-Request-Id) — they are not included in the response body.

choices[0].message​

FieldTypeDescription
rolestringAlways "assistant"
contentstring | nullConcatenated text response. null if only media
audioobject | undefinedAudio output (first audio item). See below
imageobject | undefinedImage output (first image item). See below
x_buddy_audiosarray | undefinedBuddyPro extension. All audio items, in order. Present only when more than one audio item is returned; x_buddy_audios[0] equals audio. Each item has the same shape as message.audio.
x_buddy_imagesarray | undefinedBuddyPro extension. All image items, in order. Present only when more than one image item is returned; x_buddy_images[0] equals image. Each item has the same shape as message.image.
x_buddy_commandResultobject | undefinedStructured slash-command result — present only with x_buddy_commandResultMode: "deterministic". See Deterministic Command Results
Returning all media

The singular audio / image fields stay single objects for OpenAI SDK compatibility (SDKs deserialize message.audio into a typed object). When a response carries more than one item of a type, the complete ordered set is additionally provided in the x_buddy_audios / x_buddy_images arrays — OpenAI SDK clients simply ignore these extension fields. For a single (or zero) item of a type, the array is omitted and the response is unchanged. When audio output is present, transcript is set on the first audio item only.

message.audio (OpenAiAudioOutput)​

FieldTypeDescription
idstringAudio identifier: audio_{requestId}_{fileName}
datastringBase64-encoded audio data
formatstringAudio format (e.g., mp3, ogg, wav)
transcriptstring | undefinedText transcript (same as content when TTS)
media_typestringMIME type (e.g., audio/mpeg, audio/ogg)
file_namestringSuggested filename

message.image (OpenAiImageOutput — BuddyPro Extension)​

FieldTypeDescription
idstringImage identifier: image_{requestId}_{fileName}
datastringBase64-encoded image data
media_typestringMIME type (e.g., image/png)
file_namestringSuggested filename
captionstringImage caption/description

Error Response​

All errors use a structured format with an error object:

{
"error": {
"message": "Invalid or inactive API key",
"type": "authentication_error",
"statusCode": 401,
"code": "invalid_api_key",
"param": null
}
}
Important

Always inspect the response body for the error field — do not rely solely on the HTTP status code. In certain conditions, the HTTP status code may be 200 even when the response body contains an error.

Error Fields​

FieldTypeDescription
error.messagestringHuman-readable error description
error.typestringError category
error.statusCodenumberHTTP status code
error.codestring | nullMachine-readable error code
error.paramstring | nullThe request parameter that caused the error

Error Types​

HTTP StatustypeDescription
400invalid_request_errorMalformed request, missing fields, invalid content
401authentication_errorMissing or invalid API key
403permission_errorInsufficient permissions
405method_not_allowedWrong HTTP method
410goneDeprecated endpoint no longer available
429rate_limit_errorRate limit exceeded
500server_errorInternal server error

Common Error Codes​

CodeMeaning
invalid_jsonRequest body is not valid JSON
missing_required_parameterRequired field missing
missing_api_keyNo API key provided in Authorization header
invalid_api_keyAPI key not found or inactive
invalid_valueField has wrong type or invalid value
invalid_text_contentText empty or exceeds 50,000 char limit
invalid_content_typeUnknown content part type
invalid_contentContent has no usable items
invalid_media_dataMedia data invalid, download failed, or exceeds size limit
invalid_media_typeUnsupported MIME type (covers unsupported image, audio, and document MIME types)
invalid_audio_formatUnsupported audio format
invalid_image_countToo many images (max 5)
invalid_document_countToo many documents (max 5 file parts)
invalid_parameterInvalid parameter value (e.g., bad user, x_buddy_systemPrompt, x_buddy_systemPromptMode, x_buddy_rolePrompt, x_buddy_saveToHistory, or x_buddy_commandResultMode)
insufficient_permissionsAPI key lacks required permissions
endpoint_deprecatedAPI version has been deprecated and is no longer available
rate_limit_exceededMore than 30 requests/minute

Rate Limits​

  • 30 requests per minute per API key

Limitations​

LimitationDetail
No streamingStreaming is not supported yet
Single user messageOnly 1 user message in messages array (BuddyPro manages history)
No model selectionmodel field is accepted but ignored, BuddyPro has its own model implementation
No usage statsusage object is not included in responses
First media winsOnly the first audio and first image in the response are surfaced per choice
Voice not controllableaudio.voice is accepted but ignored — voice is set by the bot owner

Media Limits​

  • Max 5 images per request
  • Max 5 documents per request (file content parts) — exceeding this returns error code invalid_document_count
  • Max 40 MB per media download (URL-fetched or decoded base64 — images, audio, and documents)
  • Inline base64 is transport-bounded (~6 MB request body). The whole request body must fit within a ~6 MB request-body size limit, so inline base64 media — an image_url data URI, input_audio.data, or file_data — is effectively capped near ~4 MB of actual file (base64 adds ~33%), well below the 40 MB cap above. For larger files send a URL instead (image_url HTTPS URL, input_audio with type: "url", or file.url): it is fetched server-side and gets the full 40 MB budget.
  • Max 50,000 characters per text content part
  • Supported document types: PDF, Word (.doc/.docx), Excel (.xls/.xlsx), PowerPoint (.ppt/.pptx), plain text, CSV, HTML, Markdown, JSON, XML, and the video types mp4/quicktime/webm/avi/mpeg (transcribed). Unsupported MIME types return invalid_media_type. See Document / File (file)

Quick Start​

curl — Text​

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "user", "content": "What is the weather like today?" }
]
}'

curl — Multimodal (Image + Text)​

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "What is in this image?" },
{ "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }
]
}
]
}'

curl — Document (File + Text)​

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": [
{ "type": "text", "text": "Summarize this report." },
{ "type": "file", "file": { "filename": "report.pdf", "file_data": "data:application/pdf;base64,<base64>" } }
]
}
]
}'

curl — Audio Input with TTS Output​

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"modalities": ["text", "audio"],
"audio": { "format": "mp3" },
"messages": [
{
"role": "user",
"content": [
{ "type": "input_audio", "input_audio": { "data": "<base64>", "format": "mp3" } }
]
}
]
}'

curl — Named User (Isolation)​

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"user": "test-user-joe",
"messages": [
{ "role": "user", "content": "Hello!" }
]
}'

curl — Stateless Mode​

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"x_buddy_saveToHistory": false,
"messages": [
{ "role": "user", "content": "What is 2 + 2?" }
]
}'

curl — Custom Role Prompt​

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"x_buddy_rolePrompt": "You are a minimalist poet who specializes in technical haiku.",
"messages": [
{ "role": "user", "content": "Write a haiku about code" }
]
}'

curl — Custom System Prompt​

curl -X POST https://api.buddypro.ai/v1/chat/completions \
-H "Authorization: Bearer bapi_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"x_buddy_systemPrompt": "You are a helpful coding assistant. Only answer programming questions.",
"x_buddy_systemPromptMode": "replace",
"messages": [
{ "role": "user", "content": "How do I reverse a string in JavaScript?" }
]
}'

Important Notes​

  • Do not send conversation history — send only the current user message. BuddyPro stores and manages conversation context internally.
  • By default, API requests use the profile that generated the API key — conversations are saved to its history and contribute to its memory. Use /test before key generation to bind the key to a test account. Use the user field for additional isolated profiles.
  • Use x_buddy_saveToHistory: false for stateless Q&A — nothing is stored to chat history, memory, or profile.
  • Named test user profiles (user field) are not private from the API key owner. Do not use them for real people's private data.
  • The API processes requests synchronously — the response is returned when BuddyPro finishes generating.
  • SSRF protection: URLs pointing to private/internal network addresses are blocked.

Version History​

VersionReleasedChanges
1.42026-09-18Document (file) content part with inline file_data or file.url; x_buddy_images / x_buddy_audios arrays when a response carries more than one image or audio item; invalid_document_count; Media Limits reworked (documents, inline-base64 transport bound)
1.32026-09-10Deterministic Command Results (x_buddy_commandResultMode, result statuses, errorCode / param, command-exchange history rules); Key rights — user-rights Owner keys, plain-user cap on End-user keys
1.22026-07-17End-user API reseller model — owner opt-in gate, reseller markup (/setApiClientMarkup), HTTP 402 mapped to payment_required
1.12026-04-25First versioned release — user isolation, x_buddy_saveToHistory, role stripping