# Embeddings Source: https://docs.nano-gpt.com/api-reference/embeddings Complete guide to text embeddings API ## Overview NanoGPT provides a fully OpenAI-compatible embeddings API that offers access to both OpenAI's industry-leading embedding models and a curated selection of alternative embedding models at competitive prices. Our API supports 20+ embedding models (and this list changes over time); use `GET /api/v1/embedding-models` for the source-of-truth list. ## Quick Start ```python theme={null} from openai import OpenAI # Initialize client pointing to NanoGPT client = OpenAI( api_key="YOUR_NANOGPT_API_KEY", base_url="https://nano-gpt.com/api/v1" ) # Create embedding response = client.embeddings.create( input="Your text to embed", model="text-embedding-3-small" ) embedding = response.data[0].embedding print(f"Embedding has {len(embedding)} dimensions") ``` ## Available Models ### OpenAI Models | Model | Dimensions | Max Tokens | Price/1M tokens | Features | | ------------------------ | ---------- | ---------- | --------------- | ------------------------------------------------ | | `text-embedding-3-small` | 1536 | 8191 | \$0.02 | Dimension reduction support, most cost-effective | | `text-embedding-3-large` | 3072 | 8191 | \$0.13 | Dimension reduction support, highest performance | | `text-embedding-ada-002` | 1536 | 8191 | \$0.10 | Legacy model, no dimension reduction | ### Alternative Models #### Multilingual Models | Model | Dimensions | Price/1M tokens | Description | | -------------- | ---------- | --------------- | ------------------------------ | | `BAAI/bge-m3` | 1024 | \$0.01 | Excellent multilingual support | | `jina-clip-v1` | 768 | \$0.04 | Multimodal CLIP embeddings | #### Language-Specific Models | Model | Language | Dimensions | Price/1M tokens | | ---------------------------- | -------- | ---------- | --------------- | | `BAAI/bge-base-en-v1.5` | English | 768 | \$0.01 | | `BAAI/bge-large-en-v1.5` | English | 1024 | \$0.01 | | `BAAI/bge-large-zh-v1.5` | Chinese | 1024 | \$0.01 | | `jina-embeddings-v2-base-en` | English | 768 | \$0.05 | | `jina-embeddings-v2-base-de` | German | 768 | \$0.05 | | `jina-embeddings-v2-base-zh` | Chinese | 768 | \$0.05 | | `jina-embeddings-v2-base-es` | Spanish | 768 | \$0.05 | #### Specialized Models | Model | Use Case | Dimensions | Price/1M tokens | | -------------------------------------- | --------- | ---------- | --------------- | | `BAAI/bge-reranker-large` | Reranking | 1024 | \$0.01 | | `jina-embeddings-v2-base-code` | Code | 768 | \$0.05 | | `Baichuan-Text-Embedding` | General | 1024 | \$0.088 | | `netease-youdao/bce-embedding-base_v1` | General | 1024 | \$0.02 | | `zhipu-embedding-2` | Chinese | 1024 | \$0.07 | | `Qwen/Qwen3-Embedding-0.6B` | General | 1024 | \$0.01 | | `Qwen/Qwen3-Embedding-4B` | General | 1536 | \$0.03 | | `Qwen/Qwen3-Embedding-8B` | General | 1536 | \$0.05 | | `jina-embeddings-v3` | General | 1024 | \$0.10 | | `jina-embeddings-v4` | General | 2048 | \$0.10 | | `gemini-embedding-001` | General | 3072 | \$0.15 | | `doubao-embedding-large-text-240915` | General | 4096 | \$0.10 | ## API Endpoints ### Create Embeddings **Endpoint:** `POST https://nano-gpt.com/api/v1/embeddings` Create embeddings for one or more text inputs. ### Discover Embedding Models **Endpoint:** `GET https://nano-gpt.com/api/v1/embedding-models` List all available embedding models with detailed information. ## Advanced Features ### Batch Processing Process multiple texts efficiently in a single request: ```python theme={null} texts = [ "First text to embed", "Second text to embed", "Third text to embed" ] response = client.embeddings.create( input=texts, # Pass array of strings model="text-embedding-3-small" ) # Access embeddings by index for i, data in enumerate(response.data): print(f"Text {i}: {len(data.embedding)} dimensions") ``` ### Dimension Reduction Reduce embedding dimensions for faster similarity comparisons (supported models only): ```python theme={null} # Reduce dimensions to 256 for faster processing response = client.embeddings.create( input="Your text here", model="text-embedding-3-small", dimensions=256 # Reduce from 1536 to 256 ) ``` Supported models for dimension reduction: * `text-embedding-3-small` * `text-embedding-3-large` * `Qwen/Qwen3-Embedding-0.6B` ### Base64 Encoding For more efficient data transfer, request base64-encoded embeddings: ```python theme={null} response = client.embeddings.create( input="Your text here", model="text-embedding-3-small", encoding_format="base64" # Returns base64-encoded bytes ) ``` ## Use Cases ### Semantic Search Build powerful search systems that understand meaning: ```python theme={null} import numpy as np from sklearn.metrics.pairwise import cosine_similarity # Create embeddings for your documents documents = ["Document 1 text", "Document 2 text", "Document 3 text"] doc_embeddings = [] for doc in documents: response = client.embeddings.create(input=doc, model="text-embedding-3-small") doc_embeddings.append(response.data[0].embedding) # Create embedding for search query query = "Search query text" query_response = client.embeddings.create(input=query, model="text-embedding-3-small") query_embedding = query_response.data[0].embedding # Calculate similarities similarities = cosine_similarity([query_embedding], doc_embeddings)[0] # Find most similar documents top_matches = np.argsort(similarities)[::-1][:3] for idx in top_matches: print(f"Document {idx}: {similarities[idx]:.3f} similarity") ``` ### RAG (Retrieval Augmented Generation) Enhance LLM responses with relevant context: ```python theme={null} # 1. Embed and store your knowledge base knowledge_base = [ {"text": "Company founded in 2020...", "embedding": None}, {"text": "Product features include...", "embedding": None}, ] for item in knowledge_base: response = client.embeddings.create( input=item["text"], model="text-embedding-3-small" ) item["embedding"] = response.data[0].embedding # 2. For a user query, find relevant context user_query = "When was the company founded?" query_response = client.embeddings.create( input=user_query, model="text-embedding-3-small" ) query_embedding = query_response.data[0].embedding # 3. Find most relevant facts (implement similarity search) # relevant_facts = find_similar_texts(query_embedding, knowledge_base, top_k=3) # 4. Use retrieved context with chat completion chat_response = client.chat.completions.create( model="gpt-4", messages=[ {"role": "system", "content": f"Use this context to answer: {relevant_facts}"}, {"role": "user", "content": user_query} ] ) ``` ### Clustering & Classification Group similar texts or classify content: ```python theme={null} from sklearn.cluster import KMeans # Create embeddings for texts texts = ["Text 1", "Text 2", "Text 3", ...] embeddings = [] for text in texts: response = client.embeddings.create(input=text, model="text-embedding-3-small") embeddings.append(response.data[0].embedding) # Cluster embeddings kmeans = KMeans(n_clusters=5) clusters = kmeans.fit_predict(embeddings) # Each text now has a cluster assignment for text, cluster_id in zip(texts, clusters): print(f"'{text}' belongs to cluster {cluster_id}") ``` ### Duplicate Detection Find similar or duplicate content: ```python theme={null} def find_duplicates(texts, threshold=0.95): embeddings = [] # Generate embeddings for text in texts: response = client.embeddings.create( input=text, model="text-embedding-3-small" ) embeddings.append(response.data[0].embedding) # Calculate pairwise similarities similarities = cosine_similarity(embeddings) # Find duplicates duplicates = [] for i in range(len(texts)): for j in range(i+1, len(texts)): if similarities[i][j] > threshold: duplicates.append((i, j, similarities[i][j])) return duplicates ``` ## Model Selection Guide ### By Use Case | Use Case | Recommended Model | Rationale | | ---------------------- | ------------------------------- | ------------------------------------- | | General English text | `text-embedding-3-small` | Best price/performance ratio | | Maximum accuracy | `text-embedding-3-large` | Highest quality embeddings | | Multilingual content | `BAAI/bge-m3` | Excellent cross-language performance | | Code embeddings | `jina-embeddings-v2-base-code` | Specialized for programming languages | | Budget-conscious | `BAAI/bge-large-en-v1.5` | Just \$0.01/1M tokens | | Chinese content | `BAAI/bge-large-zh-v1.5` | Optimized for Chinese | | Fast similarity search | Models with dimension reduction | Can reduce dimensions for speed | ### By Requirements **Need fastest search?** * Use models supporting dimension reduction * Reduce to 256-512 dimensions * Trade small accuracy loss for 2-4x speed improvement **Need highest accuracy?** * Use `text-embedding-3-large` * Keep full 3072 dimensions * Best for critical applications **Processing many languages?** * Use `BAAI/bge-m3` for general multilingual * Use language-specific Jina models for best per-language performance **Working with code?** * Use `jina-embeddings-v2-base-code` * Optimized for programming language semantics ## Best Practices ### Performance Optimization 1. **Batch Requests**: Send up to 2048 texts in a single request 2. **Use Dimension Reduction**: Reduce dimensions when exact precision isn't critical 3. **Cache Embeddings**: Store computed embeddings to avoid re-processing 4. **Choose Appropriate Models**: Don't use 3072-dimension models if 768 suffices ### Cost Optimization 1. **Monitor Usage**: Track the `usage` field in responses 2. **Start Small**: Begin with `text-embedding-3-small` before upgrading 3. **Implement Caching**: Avoid re-embedding identical content 4. **Batch Processing**: Reduce API call overhead ### Quality Optimization 1. **Preprocess Text**: Clean and normalize text before embedding 2. **Consider Context**: Include relevant context in the text to embed 3. **Test Different Models**: Compare performance for your specific use case 4. **Use Appropriate Similarity Metrics**: Cosine similarity for most cases ## Integration Examples ### JavaScript/TypeScript ```javascript theme={null} import OpenAI from 'openai'; // Initialize client const openai = new OpenAI({ apiKey: 'YOUR_NANOGPT_API_KEY', baseURL: 'https://nano-gpt.com/api/v1' }); // Create embedding const response = await openai.embeddings.create({ input: "Your text to embed", model: "text-embedding-3-small" }); const embedding = response.data[0].embedding; console.log(`Embedding has ${embedding.length} dimensions`); ``` ### cURL ```bash theme={null} curl https://nano-gpt.com/api/v1/embeddings \ -H "Authorization: Bearer YOUR_NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": "Your text to embed", "model": "text-embedding-3-small" }' ``` ### Direct API Usage ```python theme={null} import requests import json headers = { "Authorization": "Bearer YOUR_NANOGPT_API_KEY", "Content-Type": "application/json" } data = { "input": "Your text to embed", "model": "text-embedding-3-small" } response = requests.post( "https://nano-gpt.com/api/v1/embeddings", headers=headers, json=data ) result = response.json() embedding = result["data"][0]["embedding"] ``` ## Rate Limits & Error Handling ### Rate Limits Rate limits vary by endpoint and account. See [Rate Limits](/api-reference/miscellaneous/rate-limits). ### Error Codes | Code | Description | Solution | | ---- | -------------------------- | ---------------------------------- | | 401 | Invalid or missing API key | Check your API key | | 400 | Invalid request parameters | Verify model name and input format | | 429 | Rate limit exceeded | Implement exponential backoff | | 500 | Server error | Retry with exponential backoff | ### Error Response Format ```json theme={null} { "error": { "message": "Invalid model specified", "type": "invalid_request_error", "param": "model", "code": null } } ``` For a general guide across NanoGPT APIs, see [Error Handling](/api-reference/miscellaneous/error-handling). ## Migration from OpenAI Switching from OpenAI to NanoGPT is seamless: ```python theme={null} # OpenAI client = OpenAI(api_key="sk-...") # NanoGPT (just change base_url and api_key) client = OpenAI( api_key="YOUR_NANOGPT_API_KEY", base_url="https://nano-gpt.com/api/v1" ) # All other code remains exactly the same! ``` ## Pricing Summary | Price Range | Models | Best For | | --------------- | -------------------------------------- | --------------------- | | \$0.01/1M | BAAI models, Qwen | Budget applications | | \$0.02/1M | text-embedding-3-small, netease-youdao | Balanced performance | | \$0.04-0.05/1M | Jina models | Specialized use cases | | \$0.07-0.088/1M | zhipu, Baichuan | Specific requirements | | \$0.10/1M | ada-002 | Legacy compatibility | | \$0.13/1M | text-embedding-3-large | Maximum performance | ## Additional Resources * [Embeddings Endpoint Reference](/api-reference/endpoint/embeddings) * [Embedding Models List](/api-reference/endpoint/embedding-models) * [Text Generation Guide](/api-reference/text-generation) * [Quickstart Guide](/quickstart) # AI Detection Source: https://docs.nano-gpt.com/api-reference/endpoint/ai-detection POST https://nano-gpt.com/api/v1/ai-detection AI-text and plagiarism detection endpoint ## Overview Use `POST /api/v1/ai-detection` to run AI-text detection or plagiarism detection on a text input. ## Endpoint ```text theme={null} POST https://nano-gpt.com/api/v1/ai-detection ``` ## Authentication An API key is required. * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Request Body ```json theme={null} { "text": "Text to analyze...", "mode": "ai" } ``` ## Parameters | Field | Type | Required | Default | Description | | ------ | ------ | -------- | ------- | --------------------------------------------------- | | `text` | string | Yes | - | Text to analyze. | | `mode` | string | No | `ai` | Detection mode. Allowed values: `ai`, `plagiarism`. | ## Modes | Mode | Description | | ------------ | --------------------- | | `ai` | AI-text detection. | | `plagiarism` | Plagiarism detection. | ## Response ```json theme={null} { "object": "ai_detection.result", "mode": "ai", "model": "pangram-ai-detection", "content": "Human-readable summary", "result": { "score": 0.12 }, "word_count": 250, "usage": { "prompt_tokens": 300, "completion_tokens": 50, "total_tokens": 350 }, "pricing": { "amount": 0.01, "currency": "USD" } } ``` ## Example ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/ai-detection \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Text to analyze...", "mode": "ai" }' ``` ## Common Errors | Code | Description | | ------------------------ | ---------------------------------------------------------- | | `missing_api_key` | API key is required. | | `invalid_json` | Request body is not valid JSON. | | `invalid_mode` | `mode` is not `ai` or `plagiarism`. | | `missing_text` | `text` is required. | | `model_not_allowed` | Detection model is not available for the account or route. | | `paid_features_disabled` | Paid API features are disabled for the account. | | `insufficient_balance` | Account balance is too low for the request. | # Audio Models Source: https://docs.nano-gpt.com/api-reference/endpoint/audio-models GET https://nano-gpt.com/api/v1/audio-models List available text-to-speech and speech-to-text models ## Overview Use `GET /api/v1/audio-models` to discover the currently available audio models. The response includes both text-to-speech (TTS) and speech-to-text (STT) models. This endpoint is cacheable. Refresh it periodically and do not hardcode audio model capabilities in your client. ## Endpoint ```text theme={null} GET https://nano-gpt.com/api/v1/audio-models ``` ## Authentication Authentication is optional. * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Query Parameters | Parameter | Type | Default | Description | | ---------- | ------- | ------- | ----------------------------------------------------------------------------- | | `detailed` | boolean | `true` | Include names, descriptions, pricing, capabilities, and supported parameters. | | `type` | string | `all` | Filter by audio model type. Allowed values: `all`, `tts`, `stt`. | ## Response ```json theme={null} { "object": "list", "data": [ { "id": "tts-model-id", "object": "model", "name": "Display name", "description": "Model description", "architecture": { "modality": "audio", "input_modalities": ["text"], "output_modalities": ["audio"] }, "pricing": { "currency": "USD" }, "capabilities": { "text_to_speech": true, "speech_to_text": false }, "supported_parameters": {} } ], "meta": { "count": 1, "generated_at": "2026-05-07T12:00:00.000Z" } } ``` When `type` is not `all`, `meta` may include the active filter. ## Model Types | Type | Description | | ----- | -------------------------------------------------------------------------------- | | `tts` | Text-to-speech models for `POST /api/v1/audio/speech` and related TTS endpoints. | | `stt` | Speech-to-text models for transcription endpoints. | | `all` | Both TTS and STT models. | ## Example ```bash theme={null} curl "https://nano-gpt.com/api/v1/audio-models?type=stt&detailed=true" \ -H "x-api-key: $NANOGPT_API_KEY" ``` ## Notes * Supported parameters vary by model, including voices, formats, streaming support, file formats, language support, diarization, timestamps, and max input size. * Use this endpoint instead of hardcoding audio model capabilities. * The response is cacheable, but model availability can change. # v1/audio/transcriptions (STT) Source: https://docs.nano-gpt.com/api-reference/endpoint/audio-transcriptions POST https://nano-gpt.com/api/v1/audio/transcriptions OpenAI-compatible speech-to-text transcription endpoint ## Overview NanoGPT provides a drop-in OpenAI-compatible endpoint for speech-to-text (STT) transcription. ## Endpoint ```text theme={null} POST https://nano-gpt.com/api/v1/audio/transcriptions ``` ## Authentication Use either header: * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Request Formats ### 1) Multipart upload (OpenAI-compatible) Send `multipart/form-data` with: * `file` (required): audio (or video for supported models) * `model` (required): STT model ID * `language` (optional): language code (default: auto-detect) ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/audio/transcriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -F file=@audio.mp3 \ -F model=Whisper-Large-V3 \ -F language=en ``` ### 2) JSON with URL ```json theme={null} { "model": "Whisper-Large-V3", "file_url": "https://example.com/audio.mp3", "language": "en" } ``` NanoGPT accepts `file_url` or `audio_url` for URL-based transcription. ## Supported Models (Examples) Model availability changes; use `GET /api/v1/models?detailed=true` for discovery. | Model | Notes | | --------------------------- | -------------------------------------------- | | `Whisper-Large-V3` | High-accuracy transcription | | `Wizper` | Fast processing | | `Elevenlabs-STT` | Speaker diarization + audio event tagging | | `gpt-4o-mini-transcribe` | Improved accuracy vs Whisper (OpenAI-family) | | `openai-whisper-with-video` | Accepts video files (MP4, MOV, etc.) | ### Voice Cloning (via the same endpoint) Some special model IDs run voice-cloning workflows instead of returning plain transcription text: * `qwen-voice-clone` — returns a reusable speaker embedding URL * `minimax-voice-clone` — returns a reusable custom voice ID (and/or preview output) ## Response ```json theme={null} { "text": "The transcribed text goes here.", "language": "en", "duration": 45.2 } ``` ## Supported Formats * Audio: MP3, OGG, WAV, M4A, AAC * Video (model-dependent): MP4, MOV, AVI, MKV, WEBM ## Example (Python, OpenAI SDK) ```python theme={null} from openai import OpenAI client = OpenAI( base_url="https://nano-gpt.com/api/v1", api_key="YOUR_API_KEY" ) with open("audio.mp3", "rb") as audio_file: transcript = client.audio.transcriptions.create( model="Whisper-Large-V3", file=audio_file ) print(transcript.text) ``` ## See Also * NanoGPT transcription workflows: `api-reference/endpoint/transcribe.mdx` * Full STT guide and model list: `api-reference/speech-to-text.mdx` # Batch API Source: https://docs.nano-gpt.com/api-reference/endpoint/batches Run high-volume chat completions and Responses API requests asynchronously ## Overview The NanoGPT Batch API runs many independent requests asynchronously at a lower token price than the equivalent synchronous calls. It is a good fit for classification, summarization, evals, synthetic data, document processing, and image analysis where immediate results are not required. You can submit a batch in either of two ways: * **File-backed API:** upload JSONL, create a batch, poll its status, then download output and error files. This OpenAI-compatible workflow is best for large or reusable inputs. * **Inline API:** send the requests in the batch creation body and receive the results when polling the completed batch. This is simpler for smaller jobs. Both workflows support rows targeting `/v1/chat/completions` or `/v1/responses`. All rows in one batch must use the same endpoint and the same model. Batch requests are non-streaming and the only supported completion window is `24h`. ## Authentication and base URLs All requests require an API key: ```http theme={null} Authorization: Bearer $NANOGPT_API_KEY ``` Use the dedicated API host for batch operations: | Workflow | Base URL | | ----------- | ----------------------------------- | | File-backed | `https://api.nano-gpt.com/api/v1` | | Inline | `https://api.nano-gpt.com/api/beta` | Use `api.nano-gpt.com` for uploads. The main website host can reject larger multipart requests before they reach the Batch API. ## Supported row endpoints Each request in a batch must target one of these endpoints: * `/v1/chat/completions` * `/v1/responses` Completions, embeddings, image generation, audio, video, transcription, TTS, moderation, and other endpoints cannot be used as batch rows. ## Model support ### Chat Completions batches `/v1/chat/completions` batches support selected direct OpenAI, Claude, Gemini, managed, and Fireworks Batch API models. Current managed examples include MiniMax M3, GLM 5.1 and 5.2, DeepSeek V4 Pro, and Kimi K2.7 Code. Supported Fireworks Batch API model IDs include: * `accounts/fireworks/models/deepseek-v4-flash` * `accounts/fireworks/models/deepseek-v4-pro` * `accounts/fireworks/models/glm-5p2` * `accounts/fireworks/models/gpt-oss-120b` * `accounts/fireworks/models/gpt-oss-20b` * `accounts/fireworks/models/inkling` * `accounts/fireworks/models/kimi-k2p6` * `accounts/fireworks/models/kimi-k2p7-code` * `accounts/fireworks/models/kimi-k3` * `accounts/fireworks/models/minimax-m2p7` * `accounts/fireworks/models/minimax-m3` * `accounts/fireworks/models/muse-glimmer-30b` * `accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b` * `accounts/fireworks/models/nemotron-3-ultra-nvfp4` * `accounts/fireworks/models/qwen3p6-plus` * `accounts/fireworks/models/qwen3p7-plus` The `:thinking` suffix is accepted for Fireworks model IDs where a thinking variant is configured. Claude thinking aliases are also accepted for compatible models; a numeric thinking budget must be lower than `max_tokens`. Model availability changes. Validate a small batch before submitting a large job; an unsupported model returns an `unsupported_model` error. ### Responses batches `/v1/responses` batches currently support direct OpenAI models only. The `openai/` prefix is accepted. `gpt-5.2-pro` and `gpt-5.4-pro`, including dated snapshots, are not available through the upstream Responses Batch API and are rejected. Responses rows support function and custom tools, structured text output, and remote or data-URL image inputs. Provider-hosted tools and stateful Responses features are not supported in batches. ## File-backed API ### Endpoints * `POST /files` * `GET /files/{file_id}` * `GET /files/{file_id}/content` * `POST /batches` * `GET /batches/{batch_id}` * `GET /batches` * `POST /batches/{batch_id}/cancel` ### JSONL rules Each non-empty line must be a JSON object with: * a unique, non-empty `custom_id` * `method: "POST"` * `url` set to `/v1/chat/completions` or `/v1/responses` * a `body` object containing the model and endpoint-specific input All rows must use the same endpoint and model. `stream: true` is rejected. Chat Completions rows require a non-empty `messages` array and a positive `max_tokens` or `max_completion_tokens`. They support text and compatible `image_url` content. Image URLs may use HTTP, HTTPS, or base64 data URLs for PNG, JPEG, GIF, and WebP images. Responses rows require a non-empty `input` and an integer `max_output_tokens` of at least `16`. ### Chat Completions example ```jsonl theme={null} {"custom_id":"request-1","method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4.1-mini","messages":[{"role":"user","content":"Summarize this in one sentence: Batch APIs are useful for offline jobs."}],"max_tokens":64}} {"custom_id":"request-2","method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4.1-mini","messages":[{"role":"user","content":"Classify this review as positive or negative: I loved the product."}],"max_tokens":16}} ``` ### Responses example ```jsonl theme={null} {"custom_id":"response-1","method":"POST","url":"/v1/responses","body":{"model":"gpt-4.1-mini","input":"Extract three keywords from: Batch APIs process independent prompts asynchronously.","max_output_tokens":64}} {"custom_id":"response-2","method":"POST","url":"/v1/responses","body":{"model":"gpt-4.1-mini","input":"Reply with a JSON object containing a one-sentence summary.","text":{"format":{"type":"json_schema","name":"summary","strict":true,"schema":{"type":"object","properties":{"summary":{"type":"string"}},"required":["summary"],"additionalProperties":false}}},"max_output_tokens":128}} ``` ### Upload the file ```bash theme={null} curl https://api.nano-gpt.com/api/v1/files \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -F purpose=batch \ -F file=@batch.jsonl ``` ### Create the batch Set `endpoint` to the same endpoint used by every JSONL row: ```bash theme={null} curl https://api.nano-gpt.com/api/v1/batches \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input_file_id": "file_abc123", "endpoint": "/v1/responses", "completion_window": "24h" }' ``` ### Poll, list, cancel, and download ```bash theme={null} # Retrieve one batch curl https://api.nano-gpt.com/api/v1/batches/batch_abc123 \ -H "Authorization: Bearer $NANOGPT_API_KEY" # List batches; optional query parameters are limit and after curl https://api.nano-gpt.com/api/v1/batches \ -H "Authorization: Bearer $NANOGPT_API_KEY" # Best-effort cancellation curl -X POST https://api.nano-gpt.com/api/v1/batches/batch_abc123/cancel \ -H "Authorization: Bearer $NANOGPT_API_KEY" # Download JSONL output curl https://api.nano-gpt.com/api/v1/files/file_output123/content \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` Batch statuses are `validating`, `in_progress`, `finalizing`, `completed`, `failed`, `expired`, `cancelling`, and `cancelled`. A completed batch normally has an `output_file_id`; row failures may also produce an `error_file_id`. ## Inline API Inline batches accept up to 10,000 requests, 20 MiB of normalized input, and 250,000 aggregate requested output tokens. Use the file-backed API for larger inputs. ```bash theme={null} curl https://api.nano-gpt.com/api/beta/batches \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "endpoint": "/v1/responses", "model": "gpt-4.1-mini", "requests": [ { "custom_id": "response-1", "body": { "input": "Summarize this text in one sentence." } } ] }' ``` Creation returns `202 Accepted`. Poll `GET /api/beta/batches/{batch_id}` and read `results` after completion. Cancel an active job with `POST /api/beta/batches/{batch_id}/cancel`. The batch-level model is inherited by every request body. If an inline row omits its endpoint-specific output cap, NanoGPT applies a 4096-token default. File-backed rows must always include the output cap explicitly. ## Responses Batch restrictions Responses Batch is stateless and executes directly through the upstream batch service. NanoGPT forces `store: false` and rejects: * `previous_response_id`, `conversation`, and `background: true` * reusable `prompt` references * `input_file`, `item_reference`, video inputs, and `input_image.file_id` * provider-hosted tools; function and custom tools remain supported * NanoGPT-only features such as Advisor, memory, scraping, retention overrides, provider or BYOK controls, caching controls, and billing overrides Remote HTTP(S) image URLs and image data URLs are accepted. Structured output through the Responses `text.format` field is supported. ## Billing Batch jobs use NanoGPT account balance and do not use subscription included tokens. At creation, NanoGPT checks the balance against a conservative maximum-liability estimate based on the input and output caps. Completed usage is charged once after the batch reaches a terminal state. If there is no billable usage, no usage charge is created. Supported batch token usage is priced 50% below the equivalent synchronous request. Non-token charges, where supported, keep their normal rate. Use the live pricing page or pricing API as the source of truth. ## Common errors * **Unsupported endpoint:** use `/v1/chat/completions` or `/v1/responses`, consistently across all rows. * **Missing output cap:** add `max_tokens` or `max_completion_tokens` for Chat Completions, or `max_output_tokens >= 16` for Responses. * **Mixed model:** every row must use the same model. * **Streaming unsupported:** remove `stream: true`. * **Unsupported Responses model:** choose a direct OpenAI model supported by the upstream Batch API. * **Unsupported Responses field:** remove stateful features, provider-hosted tools, file references, or NanoGPT-only extensions. # Character Models Source: https://docs.nano-gpt.com/api-reference/endpoint/character-models GET /v1/character-models ## Overview `/api/v1/character-models` returns approved public characters that are enabled for API use in a model-list-compatible shape. Use this endpoint when you want to sync available public characters into tools that already understand model list responses. ## Endpoint ``` GET https://nano-gpt.com/api/v1/character-models ``` Query parameters: | Field | Type | Description | | ---------- | ------- | -------------------------------------------- | | `detailed` | boolean | Include slug and extended character metadata | | `cursor` | string | Pagination cursor | | `limit` | number | Page size | ## Example ```bash theme={null} curl "https://nano-gpt.com/api/v1/character-models?detailed=true" ``` Response: ```json theme={null} { "object": "list", "data": [ { "id": "11111111-1111-1111-1111-111111111111", "object": "model", "created": 1760000000, "owned_by": "nanogpt", "character": { "character_id": "11111111-1111-1111-1111-111111111111", "slug": "helpful-guide", "base_model_id": "deepseek-ai/DeepSeek-V3-0324", "content_rating": "sfw", "tags": ["writing"], "rating_avg": 4.8, "rating_count": 12 } } ], "next_cursor": null } ``` Pass the returned `character.character_id` as `character_id` to the Responses API. # Characters Source: https://docs.nano-gpt.com/api-reference/endpoint/characters GET /v1/characters ## Overview Characters are reusable AI personas with a base model, system prompt, optional first message, tags, content rating, and visibility controls. The website uses these same endpoints for importing, publishing, browsing, rating, and adapting character cards. ## Authentication Use either header for private writes: * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` Public catalog reads do not require authentication. Private reads and writes require the owning session or API key. ## List Public Characters ``` GET https://nano-gpt.com/api/v1/characters ``` Query parameters: | Field | Type | Description | | -------------------- | ------------------------- | -------------------------------------------- | | `q` | string | Search name, summary, and description | | `tags` | string or repeated string | Filter by one or more tag slugs | | `content_rating_max` | string | `sfw`, `suggestive`, `nsfw`, or `explicit` | | `sort` | string | `new`, `top_rated`, or `trending_simple` | | `cursor` | string | Pagination cursor from the previous response | | `limit` | number | Page size | Only approved public characters are returned. ## Create Character ``` POST https://nano-gpt.com/api/v1/characters ``` ```json theme={null} { "name": "Helpful Guide", "summary": "A concise assistant for technical writing", "description": "Helps plan, edit, and tighten technical docs.", "base_model_id": "deepseek-ai/DeepSeek-V3-0324", "system_prompt": "You are a concise technical writing assistant.", "first_message": "What are we editing today?", "visibility": "private", "allow_api": false, "creator_content_rating": "sfw", "tags": ["writing", "productivity"] } ``` Visibility values: | Value | Behavior | | ---------- | --------------------------------------------------------------- | | `private` | Only the owner can see and use it | | `unlisted` | Usable by direct ID or slug when permitted, not listed publicly | | `public` | Submitted to moderation before public discovery | Public submissions are created with `moderation_status: pending` and become discoverable only after approval. ## Manage Your Characters ``` GET /api/v1/characters/mine GET /api/v1/characters/{idOrSlug} PATCH /api/v1/characters/{idOrSlug} DELETE /api/v1/characters/{idOrSlug} ``` `DELETE` archives the character instead of hard-deleting it. `PATCH` accepts the same core fields as create. Editing an approved public character sends it back to pending moderation. ## Reviews And Reports ``` PUT /api/v1/characters/{idOrSlug}/review POST /api/v1/characters/{idOrSlug}/report ``` Review body: ```json theme={null} { "rating": 5, "review_text": "Optional review text with at least 10 characters." } ``` Reviews require an authenticated account with either an active subscription or more than \$1 in account balance. USD balance and Nano balance converted to USD both count toward this threshold. Report body: ```json theme={null} { "reason": "Policy concern", "details": "Optional extra context." } ``` ## Runtime Usage The Responses API accepts `character_id`: ```json theme={null} { "character_id": "character-uuid-or-slug", "input": "Introduce yourself" } ``` When `character_id` is supplied, NanoGPT applies the character's base model, instructions, and sampler defaults unless explicitly overridden by the request. For non-owner API usage, the character must be public, approved, and have API access enabled. # Chat Completion Source: https://docs.nano-gpt.com/api-reference/endpoint/chat-completion POST /v1/chat/completions Creates a chat completion for the provided messages. The NanoGPT Advisor extension is available for non-streaming, platform-billed pay-as-you-go API-key requests that do not use client tools, structured output, inline moderation, BYOK, accountless payment, memory, or server-side content enhancements. If you are on a NanoGPT subscription and want to keep requests limited to subscription-included models (or you have no prepaid balance), use the subscription base URL: `https://nano-gpt.com/api/subscription/v1/chat/completions` (swap `/api/v1` for `/api/subscription/v1`). Provider selection is available for supported open-source models. `X-Provider` or body `provider` explicitly selects or constrains providers for the request and is always billed pay-as-you-go at the selected provider's price, including provider-selection markup. The body `provider` field accepts either the existing string form or a structured routing object with `order`, `only`, `ignore`, `sort`, `quantizations`, `min_quantization`, `max_price`, `allow_fallbacks`, and `require_parameters`. Provider-selection-capable models also support routing preference suffixes such as `:fast` and `:cheap`. For subscription users, explicit provider selection bypasses subscription coverage for that request; `X-Billing-Mode: paygo` is only needed when forcing pay-as-you-go without an explicit provider or when saved provider preferences should apply to subscription-included traffic. See [Provider Selection](/api-reference/miscellaneous/provider-selection), [Model Suffixes](/api-reference/miscellaneous/model-suffixes), and [Pay-As-You-Go Billing Override](/api-reference/miscellaneous/billing-override). **Accountless x402 payments**: Non-streaming `POST /api/v1/chat/completions` requests can be quoted without an account or API key on supported deployments when the initial quote request includes `x-x402: true`. Streaming chat has implementation coverage but is not part of the stable public accountless contract. This endpoint supports accountless x402 payments where listed by `GET /api/v1/x402/endpoints`, including Lightning L402 when advertised. See [Accountless x402 API Payments](/api-reference/miscellaneous/x402) for the full flow. **Advisor extension:** Non-streaming pay-as-you-go API-key requests can include an `advisor` object so the executor model can consult one different model before returning its final answer. Use `mode: "auto"` to let the executor decide or `mode: "required"` to require a consultation attempt. Each completed model phase is billed separately. Advisor is a NanoGPT extension, not part of the standard OpenAI request schema. See [Advisor](/api-reference/miscellaneous/advisor). ## Page map Use the jump list below to navigate the long-form reference quickly. * [Advisor extension](#advisor-extension) * [Tool calling](#tool-calling) * [Overview](#overview) * [Provider Routing Suffixes](#provider-routing-suffixes) * [Provider Routing Object](#provider-routing-object) * [Sampling & Decoding Controls](#sampling-decoding-controls) * [Temperature & Nucleus](#temperature-nucleus) * [Length & Stopping](#length-stopping) * [Penalties & Repetition Guards](#penalties-repetition-guards) * [Logit Shaping & Reproducibility](#logit-shaping-reproducibility) * [Sampling example request](#example-request-1) * [Structured Outputs (response\_format)](#structured-outputs-response-format) * [Supported Formats](#supported-formats) * [JSON Object Mode](#json-object-mode) * [JSON Schema Mode](#json-schema-mode-structured-outputs) * [Schema Requirements](#schema-requirements) * [Example Request](#example-request-2) * [Example Response](#example-response) * [Vercel AI SDK](#usage-with-vercel-ai-sdk) * [Web Search](#web-search) * [Option A: model suffixes](#option-a-model-suffixes) * [Option B: request body configuration](#option-b-request-body-configuration-recommended) * [Provider-specific options](#provider-specific-options-set-inside-websearch) * [Examples](#examples) * [Pricing by provider](#pricing-by-provider) * [Bring your own key (BYOK)](#bring-your-own-key-byok) * [Image Input](#image-input) * [Video Input](#video-input) * [Supported Forms](#supported-forms) * [Message Shape](#message-shape) * [cURL - Image URL](#curl-image-url-non-streaming) * [cURL - Base64 Data URL](#curl-base64-data-url-non-streaming) * [cURL - Streaming SSE](#curl-streaming-sse) * [Caching (Implicit and Explicit Controls)](#caching-implicit-and-explicit-controls) * [Cache-Capable Provider Routing](#cache-capable-provider-routing) * [Cache Consistency](#explicit-prompt-cache-consistency) * [Troubleshooting](#troubleshooting) * [Context Memory](#context-memory) * [Custom Context Size Override](#custom-context-size-override) * [Reasoning Streams](#reasoning-streams) * [Endpoint variants](#endpoint-variants) * [Streaming payload format](#streaming-payload-format) * [Showing or hiding reasoning](#showing-or-hiding-reasoning) * [Reasoning Effort](#reasoning-effort) * [Model suffix: :reasoning-exclude](#model-suffix-reasoning-exclude) * [Legacy delta field compatibility](#legacy-delta-field-compatibility) * [Service tiers (flex and priority)](#service-tiers-priority) * [Compressed Request Bodies](#compressed-request-bodies) * [YouTube Transcripts](#youtube-transcripts) * [Performance Benchmarks](#performance-benchmarks) * [Important Notes](#important-notes) ## Advisor extension * `advisor` (optional object): Configures one different, client-selected model that the executor may consult. The initial version is limited to non-streaming, pay-as-you-go API-key requests and cannot be combined with client tools or structured output. See [Advisor](/api-reference/miscellaneous/advisor) for the full request contract, billing, privacy, response metadata, and limitations. ## Tool calling The `/api/v1/chat/completions` endpoint supports OpenAI-compatible function calling. You can describe callable functions in the `tools` array, control when the model may invoke them, and continue the conversation by echoing `tool` role messages that reference the assistant's chosen call. ### Request parameters * `tools` (optional array): Each entry must be `{ "type": "function", "function": { "name": string, "description"?: string, "parameters"?: JSON-Schema object } }`. Only `function` tools are accepted. The serialized `tools` payload is limited to 200 KB (overrides via `TOOL_SPEC_MAX_BYTES`); violating the shape or size yields a 400 with `tool_spec_too_large`, `invalid_tool_spec`, or `invalid_tool_spec_parse`. * `tool_choice` (optional string or object): Defaults to `auto`. Set `"none"` to guarantee no tool calls (the server also drops the `tools` payload upstream), `"required"` to force the next response to be a tool call, or `{ "type": "function", "function": { "name": "your_function" } }` to pin the exact function. * `parallel_tool_calls` (optional boolean): When `true` the flag is forwarded to providers that support issuing multiple tool calls in a single turn. Models that ignore the flag fall back to sequential calls. * `messages[].tool_calls` (assistant role): Persist the tool call metadata returned by the model so future turns can see which functions were invoked. Each item uses the OpenAI shape `{ id, type: "function", function: { name, arguments } }`. * `messages[]` with `role: "tool"`: Respond to the model by sending `{ "role": "tool", "tool_call_id": "", "content": "" }`. The server drops any tool response that references an unknown `tool_call_id`, so keep the IDs in sync. * Validation behavior: If you send `tool_choice: "none"` with a `tools` array the request is accepted but the tools are omitted before hitting the model; invalid schemas or oversize payloads return the error codes above. ### Example request ```http theme={null} POST /api/v1/chat/completions { "model": "google/gemini-3-flash-preview", "messages": [ { "role": "user", "content": "What's the temperature in San Francisco right now?" } ], "tools": [ { "type": "function", "function": { "name": "lookup_weather", "description": "Fetch the current weather for a city.", "parameters": { "type": "object", "properties": { "city": { "type": "string" }, "unit": { "type": "string", "enum": ["c", "f"] } }, "required": ["city"] } } } ], "tool_choice": "auto", "parallel_tool_calls": true } ``` ### Example assistant/tool turn ```json theme={null} { "role": "assistant", "content": null, "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "lookup_weather", "arguments": "{\"city\":\"San Francisco\",\"unit\":\"f\"}" } } ] } ``` ```json theme={null} { "role": "tool", "tool_call_id": "call_abc123", "content": "{\"city\":\"San Francisco\",\"temperatureF\":58,\"conditions\":\"foggy\"}" } ``` Streaming responses emit delta events that mirror OpenAI's `tool_calls` schema, so consumers can reuse their existing parsing logic without changes. ## Overview The Chat Completion endpoint provides OpenAI-compatible chat completions. ## Provider Routing Suffixes Provider-selection-capable models support routing preference suffixes such as `:fast` and `:cheap`. See [Provider Selection > Per-Request Routing Preference](/api-reference/miscellaneous/provider-selection#per-request-routing-preference) for the full list and billing rules, or [Model Suffixes](/api-reference/miscellaneous/model-suffixes) for all suffix composition rules. ## Provider Routing Object The `provider` request body field accepts either a provider ID string or a structured object for routing controls: ```json theme={null} { "model": "model-id", "provider": { "order": ["provider-a", "provider-b"], "only": ["provider-b"], "ignore": ["provider-c"], "sort": "price", "quantizations": ["fp8", "fp16"], "min_quantization": "fp8", "max_price": { "prompt": 0.5, "completion": 2 }, "allow_fallbacks": false, "require_parameters": true }, "messages": [ { "role": "user", "content": "Hello" } ] } ``` Use [Provider Selection > Provider Routing Object](/api-reference/miscellaneous/provider-selection#provider-routing-object) for field semantics, validation behavior, and provider discovery. ## Sampling & Decoding Controls The `/api/v1/chat/completions` endpoint accepts a full set of sampling and decoding knobs. All fields are optional; omit any you want to leave at provider defaults. ### Temperature & Nucleus | Parameter | Range/Default | Description | | ------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `temperature` | 0–2 (provider default) | Classic randomness control; higher values explore more. If omitted, NanoGPT does not force a value and the routed provider/model default applies. | | `top_p` | 0–1 (default 1) | Nucleus sampling that trims to the smallest set above `top_p` cumulative probability. | | `top_k` | 1+ | Sample only from the top-k tokens each step. | | `top_a` | provider default | Blends temperature and nucleus behavior; set only if a model calls for it. | | `min_p` | 0–1 | Require each candidate token to exceed a probability floor. | | `tfs` | 0–1 | Tail free sampling; 1 disables. | | `eta_cutoff` / `epsilon_cutoff` | provider default | Drop tokens once they fall below the tail thresholds. | | `typical_p` | 0–1 | Entropy-based nucleus sampling; keeps tokens whose surprise matches expected entropy. | | `mirostat_mode` | 0/1/2 | Enable Mirostat sampling; set tau/eta when active. | | `mirostat_tau` / `mirostat_eta` | provider default | Target entropy and learning rate for Mirostat. | ### Length & Stopping | Parameter | Range/Default | Description | | ---------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `max_tokens` | 1+ (provider default) | Upper bound on generated tokens. If omitted, NanoGPT does not enforce an explicit default and the routed provider/model default applies. | | `min_tokens` | 0+ (default 0) | Minimum completion length when provider supports it. | | `stop` | string or string\[] | Stop sequences passed upstream. | | `stop_token_ids` | int\[] | Stop generation on specific token IDs (limited provider support). | | `include_stop_str_in_output` | boolean (default false) | Keep the stop sequence in the final text where supported. | | `ignore_eos` | boolean (default false) | Continue even if the model predicts EOS internally. | ### Penalties & Repetition Guards | Parameter | Range/Default | Description | | ---------------------- | ------------------ | -------------------------------------------------------------- | | `frequency_penalty` | -2 – 2 (default 0) | Penalize tokens proportional to prior frequency. | | `presence_penalty` | -2 – 2 (default 0) | Penalize tokens based on whether they appeared at all. | | `repetition_penalty` | -2 – 2 | Provider-agnostic repetition modifier; >1 discourages repeats. | | `no_repeat_ngram_size` | 0+ | Forbid repeating n-grams of the given size (limited support). | | `custom_token_bans` | int\[] | Fully block listed token IDs. | ### Logit Shaping & Reproducibility | Parameter | Range/Default | Description | | ----------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `logit_bias` | object | Map token IDs to additive logits (OpenAI-compatible). | | `logprobs` | boolean or int | Return token-level logprobs where supported. | | `prompt_logprobs` | boolean | Request logprobs on the prompt when available. | | `seed` | integer | Optional integer forwarded on model/provider routes that support seeded sampling. This may improve reproducibility but does not guarantee identical output. Results can change if NanoGPT selects a different automatic or fallback route, or if the provider changes its backend. | #### Reproducibility guidance Seeded generation is best-effort. To reduce avoidable variation: * Keep the exact model, messages, tools, and sampling settings unchanged. * Select a specific provider where possible. * Disable automatic fallbacks where supported when route consistency matters. * Use a low or zero `temperature` where supported. * Do not treat seeded output as byte-identical. Record provider, route, and system-fingerprint metadata when NanoGPT exposes reliable values. NanoGPT will publish a seed-support matrix when reliable route-level capability data is available. Until then, do not infer seed support from endpoint compatibility alone. ### Usage notes * Parameters can be combined (e.g., `temperature` + `top_p` + `top_k`), but overly narrow settings may lead to early stops. * Invalid ranges yield a 400 before reaching the provider. * Provider defaults apply to any omitted field. ### Example request ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "google/gemini-3-flash-preview", "messages": [ {"role": "user", "content": "Write a creative story about space exploration"} ], "temperature": 0.8, "top_p": 0.9, "top_k": 40, "tfs": 0.8, "typical_p": 0.95, "mirostat_mode": 2, "mirostat_tau": 5, "mirostat_eta": 0.1, "max_tokens": 500, "frequency_penalty": 0.3, "presence_penalty": 0.1, "repetition_penalty": 1.1, "stop": ["###"], "seed": 42 }' ``` ## Structured Outputs (response\_format) The `/api/v1/chat/completions` endpoint supports OpenAI-compatible structured outputs via the `response_format` parameter. This ensures the model returns valid JSON matching your specified schema. ### Supported Formats | Type | Description | | ------------- | ---------------------------------------------------------- | | `json_object` | Forces the model to return valid JSON | | `json_schema` | Forces the model to return JSON matching a specific schema | | `text` | Default text output (no constraint) | ### JSON Object Mode Request valid JSON output without a specific schema: ```json theme={null} { "model": "openai/gpt-5.1", "messages": [{"role": "user", "content": "List 3 colors as JSON"}], "response_format": {"type": "json_object"} } ``` ### JSON Schema Mode (Structured Outputs) Request JSON that conforms to a specific schema: ```json theme={null} { "model": "openai/gpt-5.1", "messages": [{"role": "user", "content": "What is 2+2?"}], "response_format": { "type": "json_schema", "json_schema": { "name": "math_answer", "strict": true, "schema": { "type": "object", "properties": { "answer": {"type": "number"}, "explanation": {"type": "string"} }, "required": ["answer", "explanation"], "additionalProperties": false } } } } ``` ### Schema Requirements When using `strict: true`: * All properties must be listed in `required` * Set `additionalProperties: false` * NanoGPT automatically transforms optional properties to be nullable for OpenAI compatibility ### Supported Models JSON schema mode works with most models including: * OpenAI models (GPT-5.1, GPT-5.2, etc.) * Anthropic Claude models * Google Gemini models * Many open-source models ### Example Request ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.1", "messages": [ {"role": "user", "content": "Generate a person profile"} ], "response_format": { "type": "json_schema", "json_schema": { "name": "person", "strict": true, "schema": { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "number"}, "skills": { "type": "array", "items": {"type": "string"} } }, "required": ["name", "age", "skills"], "additionalProperties": false } } }, "stream": false }' ``` ```python Python theme={null} import requests response = requests.post( "https://nano-gpt.com/api/v1/chat/completions", headers={ "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" }, json={ "model": "openai/gpt-5.1", "messages": [ {"role": "user", "content": "Generate a person profile"} ], "response_format": { "type": "json_schema", "json_schema": { "name": "person", "strict": True, "schema": { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "number"}, "skills": { "type": "array", "items": {"type": "string"} } }, "required": ["name", "age", "skills"], "additionalProperties": False } } } } ) data = response.json() print(data["choices"][0]["message"]["content"]) # Output: {"name": "Alice Chen", "age": 28, "skills": ["Python", "Machine Learning", "Data Analysis"]} ``` ```javascript JavaScript theme={null} const response = await fetch("https://nano-gpt.com/api/v1/chat/completions", { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ model: "openai/gpt-5.1", messages: [ { role: "user", content: "Generate a person profile" } ], response_format: { type: "json_schema", json_schema: { name: "person", strict: true, schema: { type: "object", properties: { name: { type: "string" }, age: { type: "number" }, skills: { type: "array", items: { type: "string" } } }, required: ["name", "age", "skills"], additionalProperties: false } } } }) }); const data = await response.json(); console.log(data.choices[0].message.content); // Output: {"name": "Alice Chen", "age": 28, "skills": ["Python", "Machine Learning", "Data Analysis"]} ``` ### Example Response ```json theme={null} { "id": "chatcmpl-abc123", "object": "chat.completion", "created": 1769278225, "model": "openai/gpt-5.1", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "{\"name\":\"Alice Chen\",\"age\":28,\"skills\":[\"Python\",\"Machine Learning\",\"Data Analysis\"]}" }, "finish_reason": "stop" } ] } ``` ### Usage with Vercel AI SDK The `response_format` parameter is compatible with Vercel AI SDK's `generateObject`: ```typescript theme={null} import { generateObject } from 'ai'; import { createOpenAI } from '@ai-sdk/openai'; import { z } from 'zod'; const nanogpt = createOpenAI({ baseURL: 'https://nano-gpt.com/api/v1', apiKey: 'YOUR_API_KEY', }); const { object } = await generateObject({ model: nanogpt('openai/gpt-5.1'), schema: z.object({ name: z.string(), age: z.number(), skills: z.array(z.string()), }), prompt: 'Generate a person profile', }); console.log(object); // { name: "Alice Chen", age: 28, skills: ["Python", "Machine Learning", "Data Analysis"] } ``` ### Usage Notes * Works with both streaming and non-streaming requests * The `name` field in `json_schema` is required and should describe the output * Response content is a JSON string; parse it with `JSON.parse()` in your application * Some provider-specific limitations may apply; if you encounter issues with a specific model, try an alternative ## Web Search Enable web search in two ways: model suffixes or a `webSearch` object in the request body. The legacy `linkup` object is still supported as an alias. If `webSearch.enabled` (or `linkup.enabled`) is `true`, it takes precedence over any model suffix. OpenAI native web search: GPT-5+ / o1 / o3 / o4 models use OpenAI's built-in web search automatically. No suffix is required; you can still set `webSearch.search_context_size` and `webSearch.user_location`. To force a different provider, specify a provider or suffix. If you need full direct control over the search call itself (`query`, `outputType`, date/domain filters, or structured schema output), use [Direct Web Search API (`POST /api/web`)](/api-reference/endpoint/web-search). | Use case | Recommended endpoint | | ------------------------------------------------ | ------------------------------- | | Model should answer with web context in one call | `POST /api/v1/chat/completions` | | You need raw/structured web payload control | `POST /api/web` | ### Option A: model suffixes Append one of these to your `model` value: * `:online` (default web search, standard depth) * `:online/linkup` (Linkup, standard) * `:online/linkup-deep` (Linkup, deep) * `:online/tavily` (Tavily, standard) * `:online/tavily-deep` (Tavily, deep) * `:online/brave` (Brave, standard) * `:online/brave-deep` (Brave, deep) * `:online/sofya` (Sofya, standard) * `:online/exa-fast` (Exa, fast) * `:online/exa-auto` (Exa, auto) * `:online/exa-neural` (Exa, neural) * `:online/exa-deep` (Exa, deep) * `:online/exa-instant` (Exa, instant) * `:online/exa-deep-reasoning` (Exa, deep-reasoning) * `:online/kagi` (Kagi, standard, search) * `:online/kagi-web` (Kagi, standard, web) * `:online/kagi-news` (Kagi, standard, news) * `:online/kagi-search` (Kagi, deep, search) * `:online/perplexity` (Perplexity, standard) * `:online/perplexity-deep` (Perplexity, deep) * `:online/valyu` (Valyu, standard, all sources) * `:online/valyu-deep` (Valyu, deep, all sources) * `:online/valyu-web` (Valyu, standard, web only) * `:online/valyu-web-deep` (Valyu, deep, web only) `:online` without an explicit provider uses the default web search backend (Linkup). ### Option B: request body configuration (recommended) Send a `webSearch` object in the request body. The legacy `linkup` object is accepted as an alias. This works with or without a model suffix and controls web search across all providers. `webSearch` fields: * `enabled` (boolean, required to activate web search) * `provider` (string): `linkup` | `tavily` | `brave` | `sofya` | `exa` | `kagi` | `perplexity` | `valyu` * `depth` (string): * Linkup/Tavily/Brave/Perplexity/Valyu: `standard` or `deep` * Sofya: `standard` * Exa: `fast`, `auto`, `neural`, `deep`, `instant`, `deep-reasoning` (use `standard` if you want `auto`) * Kagi: `standard` or `deep` (`search` source only) * `search_context_size` or `searchContextSize` (string, OpenAI native): `low` | `medium` | `high` (default: `medium`) * `user_location` or `userLocation` (object, OpenAI native): `{ type: "approximate", country, city, region }` * `searchType` (string, Valyu only): `all` | `web` * `kagiSource` or `kagi_source` (string, Kagi only): `web` | `news` | `search` Legacy alias example: ```json theme={null} { "linkup": { "enabled": true, "provider": "tavily", "search_context_size": "medium" } } ``` #### Provider-specific options (set inside `webSearch`) ##### Perplexity ```json theme={null} { "maxResults": 1-20, "maxTokensPerPage": number, "maxTokens": 1-1000000, "country": "string", "searchDomainFilter": ["domain1.com", "domain2.com"], "searchLanguageFilter": ["en", "de"] } ``` Limits: `searchDomainFilter` max 20 entries; `searchLanguageFilter` max 10 entries (ISO 639-1). ##### Valyu ```json theme={null} { "searchType": "all" | "web", "fastMode": boolean, "maxNumResults": 1-50, "maxPrice": number, "relevanceThreshold": 0-1, "responseLength": "short" | "medium" | "large" | "max" | number, "countryCode": "US", "includedSources": ["source1.com"], "excludedSources": ["source2.com"], "urlOnly": boolean, "category": "string" } ``` `countryCode` uses a 2-letter ISO country code. ##### Tavily ```json theme={null} { "maxResults": 0-20, "includeAnswer": boolean | "basic" | "advanced", "includeRawContent": boolean | "markdown" | "text", "includeImages": boolean, "includeImageDescriptions": boolean, "includeFavicon": boolean, "topic": "general" | "news" | "finance", "timeRange": "day" | "week" | "month" | "year", "startDate": "YYYY-MM-DD", "endDate": "YYYY-MM-DD", "chunksPerSource": 1-3, "country": "string" } ``` ##### Exa ```json theme={null} { "numResults": 1-100, "category": "company" | "research paper" | "news" | "pdf" | "github" | "tweet" | "personal site" | "people" | "financial report", "userLocation": "US", "additionalQueries": ["query2"], "startCrawlDate": "ISO 8601", "endCrawlDate": "ISO 8601", "startPublishedDate": "ISO 8601", "endPublishedDate": "ISO 8601", "includeText": ["pattern"], "excludeText": ["pattern"], "livecrawl": "never" | "fallback" | "always" | "preferred", "livecrawlTimeout": number, "subpages": number, "subpageTarget": "string" | ["strings"] } ``` ##### Sofya Sofya search returns extracted page content rather than snippets alone. Chat completions use its Search operation. For explicit query options and Sofya's Fetch, Extract, and Research operations, use the [Direct Web Search API](/api-reference/endpoint/web-search#sofya-operations). ##### OpenAI native (GPT-5.2) ```json theme={null} { "search_context_size": "low" | "medium" | "high", "user_location": { "type": "approximate", "country": "US", "city": "San Francisco", "region": "California" } } ``` ### Examples ```python Python theme={null} import requests import json BASE_URL = "https://nano-gpt.com/api/v1" API_KEY = "YOUR_API_KEY" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } # Suffix-based standard web search data = { "model": "openai/gpt-5.6-sol:online", "messages": [ {"role": "user", "content": "What are the latest developments in AI?"} ] } response = requests.post( f"{BASE_URL}/chat/completions", headers=headers, json=data ) # Request-body configuration (Exa neural) data_search = { "model": "openai/gpt-5.6-sol", "messages": [ {"role": "user", "content": "Provide a comprehensive analysis of recent AI breakthroughs"} ], "webSearch": { "enabled": True, "provider": "exa", "depth": "neural", "numResults": 10 } } ``` ```javascript JavaScript theme={null} const BASE_URL = "https://nano-gpt.com/api/v1"; const API_KEY = "YOUR_API_KEY"; // Suffix-based standard web search const response = await fetch(`${BASE_URL}/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'openai/gpt-5.6-sol:online', messages: [ { role: 'user', content: 'What are the latest developments in AI?' } ] }) }); // Request-body configuration (Exa neural) const searchResponse = await fetch(`${BASE_URL}/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'openai/gpt-5.6-sol', messages: [ { role: 'user', content: 'Provide a comprehensive analysis of recent AI breakthroughs' } ], webSearch: { enabled: true, provider: 'exa', depth: 'neural', numResults: 10 } }) }); ``` ```bash cURL theme={null} # Suffix-based standard web search curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol:online", "messages": [ {"role": "user", "content": "What are the latest developments in AI?"} ] }' # Request-body configuration (Exa neural) curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [ {"role": "user", "content": "Provide a comprehensive analysis of recent AI breakthroughs"} ], "webSearch": { "enabled": true, "provider": "exa", "depth": "neural", "numResults": 10 } }' ``` ### Pricing by provider | Provider | Standard | Deep | Notes | | ------------- | ----------------- | -------------- | ------------------------------------------- | | Linkup | \$0.006 | \$0.06 | Default provider | | Tavily | \$0.008 | \$0.016 | Good value, free tier available | | Exa | \$0.005 base | + \$0.001/page | For contents retrieval | | Kagi Web/News | \$0.002 | N/A | Cheapest for enrichment | | Kagi Search | \$0.025 | N/A | Full search mode | | Perplexity | \$0.005 | N/A | Flat rate | | Valyu | \~\$0.0015/result | Variable | Dynamic pricing | | Brave | \$0.005 | \$0.005 | Flat rate | | Sofya | \$0.01575 | N/A | Extracted page content; standard depth only | | OpenAI Native | \$0.01 + tokens | N/A | Per-call fee + model token costs | For standard NanoGPT usage, provider credentials are handled automatically. ### Bring your own key (BYOK) BYOK lets you route requests through your own upstream provider credentials. * Configure keys once: [https://nano-gpt.com/byok](https://nano-gpt.com/byok) * Opt in per request via `x-use-byok: true` or `byok.enabled: true` * Optionally force the provider via `x-byok-provider` or `byok.provider` * BYOK usage includes a **5% platform fee** (your provider bills you directly for usage) See: [Bring Your Own Key (BYOK)](/api-reference/miscellaneous/byok) #### Web search BYOK Web search BYOK availability is provider-dependent and can change over time. See the BYOK reference for the current support matrix. ### Advanced behavior (optional) * **Provider routing**: For GPT-5+ / o1 / o3 / o4 models, `:online` without an explicit provider uses OpenAI native web search. If you set `webSearch.provider` or use an explicit `:online/` suffix, that provider is used instead. * **Model suffix normalization**: `:online` (and provider/depth suffixes) are stripped from the model name before routing to the base model; the suffix only controls search behavior. * **Query formation (non-OpenAI providers)**: The search query is derived from your latest user message and may include the previous user message if the latest is short. If you need full control over the query or raw results, use the Web Search endpoint (`/api/web`). * **`scraping: true` URL handling**: When enabled, NanoGPT scans messages for public `http(s)` URLs, ignores local/private URLs, de-duplicates, and caps at 5. If no eligible URLs are found, scraping is skipped. Inline scraping in chat is billed at **\$0.0015 per successfully scraped URL**. For explicit URL lists and the standalone endpoint price (**\$0.001 per URL**), use `/scrape-urls`. ## Image Input Send images using the OpenAI‑compatible chat format. Provide image parts alongside text in the `messages` array. ### Supported Forms * Remote URL: `{"type":"image_url","image_url":{"url":"https://..."}}` * Base64 data URL: `{"type":"image_url","image_url":{"url":"data:image/png;base64,...."}}` Notes: * Prefer HTTPS URLs; some upstreams reject non‑HTTPS. If in doubt, use base64 data URLs. * Accepted mime types: `image/png`, `image/jpeg`, `image/jpg`, `image/webp`. * Inline markdown images in plain text (e.g., `![alt](data:image/...;base64,...)`) are auto‑normalized into structured parts server‑side. ### Message Shape ```json theme={null} { "role": "user", "content": [ { "type": "text", "text": "What is in this image?" }, { "type": "image_url", "image_url": { "url": "https://example.com/image.jpg" } } ] } ``` ### cURL — Image URL (non‑streaming) ```bash theme={null} curl -sS \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -X POST https://nano-gpt.com/api/v1/chat/completions \ --data '{ "model": "google/gemini-3-flash-preview", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "Describe this image in three words."}, {"type": "image_url", "image_url": {"url": "https://upload.wikimedia.org/wikipedia/commons/3/3f/Fronalpstock_big.jpg"}} ] } ], "stream": false }' ``` ### cURL — Base64 Data URL (non‑streaming) Embed your image as a data URL. Replace `...BASE64...` with your image bytes. ```bash theme={null} curl -sS \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type": "application/json" \ -X POST https://nano-gpt.com/api/v1/chat/completions \ --data '{ "model": "google/gemini-3-flash-preview", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "What is shown here?"}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,...BASE64..."}} ] } ], "stream": false }' ``` ### cURL — Streaming SSE See also: [Streaming Protocol (SSE)](/api-reference/miscellaneous/streaming-protocol). ```bash theme={null} curl -N \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -X POST https://nano-gpt.com/api/v1/chat/completions \ --data '{ "model": "google/gemini-3-flash-preview", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "Two words only."}, {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}} ] } ], "stream": true, "stream_options": { "include_usage": true } }' ``` The response streams `data: { ... }` lines until a final terminator. Usage metrics appear only when requested: set `stream_options.include_usage` to `true` for streaming responses, or send `"include_usage": true` on non-streaming calls. *Note: Prompt-caching helpers implicitly force `include_usage`, so cached requests still receive usage data without extra flags.* ## Video Input Send video clips to a compatible text or multimodal model using the canonical `video_url` content part. The URL may be a public HTTPS URL or a `data:video/*;base64,...` URL: ```json theme={null} { "model": "google/gemini-3.1-flash-lite", "messages": [{ "role": "user", "content": [ { "type": "text", "text": "Describe this clip." }, { "type": "video_url", "video_url": { "url": "https://cdn.example.com/clip.mp4", "detail": "auto" } } ] }] } ``` Compatibility aliases (`input_video`, direct `video`, and safely identifiable `input_file`/`file` blocks) are accepted, but new integrations should emit `video_url`. See the [Video Input guide](/api-reference/miscellaneous/video-input) for model discovery, source validation, limits, errors, YouTube behavior, and the Responses and Messages shapes. ### Caching (Implicit and Explicit Controls) For the full guide (supported models, thresholds, pricing, and usage fields), see [Prompt Caching](/api-reference/miscellaneous/prompt-caching). NanoGPT automatically applies implicit caching on providers/models that support it (including OpenAI, Gemini, and many open-source provider/model routes), so most requests do not need caching flags. Set top-level `caching: true` or append `:caching` / `:cache` / `:cached` to the model when you want NanoGPT to route the request to any available provider that supports prompt/input caching. This is capability-based routing: you do not need to choose a provider. If no cache-capable provider is available for the model, the request fails rather than silently using a non-caching provider. Use explicit prompt-caching controls (`prompt_caching`, `promptCaching`, and body-level `cache_control` alias, plus inline `cache_control`) when you need Claude-specific cache boundaries, TTL selection, or `prompt_caching.stickyProvider` consistency control. Top-level `caching: true` does not add Anthropic-style `cache_control` markers or configure cache TTLs. #### Cache-Capable Provider Routing Top-level `caching: true` is provider routing, not prompt-cache annotation. It requires the routed provider to be marked as prompt-caching capable for the requested provider-selection model. ```json theme={null} { "model": "model-id", "caching": true, "messages": [ { "role": "user", "content": "Hello" } ] } ``` By default, `caching: true` also enables sticky provider routing. After the first successful matching request, NanoGPT will try to use the same provider for later matching requests from the same API key or session, improving the chance of provider-side cache hits. This does not guarantee that a request will be served from cache. To require a cache-capable provider without sticky routing, set `stickyprovider: false`: ```json theme={null} { "model": "model-id", "caching": true, "stickyprovider": false, "messages": [ { "role": "user", "content": "Hello" } ] } ``` Top-level fields: | Parameter | Type | Default | Description | | ---------------- | ------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `caching` | boolean | `false` | Require a cache-capable provider for this request. If none is usable for the model, the request fails. | | `stickyprovider` | boolean | `true` when `caching: true` | Prefer the previously recorded provider for later matching cache-capable requests. Set `false` to restore non-sticky cache-capable routing. | | `stickyProvider` | boolean | Alias | CamelCase alias for top-level `stickyprovider`. Use `stickyprovider` in examples. | Equivalent model suffix: ```json theme={null} { "model": "moonshotai/kimi-k2.6:thinking:caching", "messages": [ { "role": "user", "content": "Hello" } ] } ``` For `caching: true`, routing works as follows: 1. Filter to providers that are available, not excluded by preferences, and marked as prompt-caching capable. 2. If stickiness is enabled, prefer the previously recorded provider for the same cache-relevant request shape when still usable. 3. Otherwise choose the cheapest cache-capable provider by base input + output price. 4. Use cache write/read pricing only as tie-breakers. The `prompt_caching` / `promptCaching` helper accepts these options: | Parameter | Type | Default | Description | | -------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `enabled` | boolean | — | Enable prompt caching | | `ttl` | string | `"5m"` | Cache time-to-live: `"5m"` or `"1h"` | | `cut_after_message_index` / `cutAfterMessageIndex` | integer | — | Zero-based index; cache all messages up to and including this index | | `stickyProvider` | boolean | `false` | When `true`, disable automatic failover to preserve explicit prompt-cache consistency. Returns 503 error instead of switching services. | ```python theme={null} headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } payload = { "model": "anthropic/claude-sonnet-4.6", "messages": [ { "role": "system", "content": [ { "type": "text", "text": "Reference handbook + rules of engagement.", "cache_control": {"type": "ephemeral", "ttl": "5m"} } ] }, {"role": "user", "content": "Live request goes here"} ] } requests.post("https://nano-gpt.com/api/v1/chat/completions", headers=headers, json=payload) ``` * Each `cache_control` marker caches the full prefix up to that block. Place them on every static chunk (system messages, tool definitions, large contexts) you plan to reuse. * Explicit TTL controls are `5m` and `1h` for Claude caching flows. See [Prompt Caching](/api-reference/miscellaneous/prompt-caching#pricing). * `anthropic-beta: prompt-caching-2024-07-31` is supported for compatibility (and required for Anthropic-native Claude caching flows). * For implicit-caching providers, no explicit `cache_control` markers are required. For a simpler experience, send the helper fields and NanoGPT will stamp the first *N* messages for you before forwarding upstream: ```ts theme={null} await client.chat.completions.create( { model: 'anthropic/claude-opus-4.6', messages: [ { role: 'system', content: 'Static rubric lives here.' }, { role: 'user', content: 'Additional reusable context.' }, { role: 'user', content: 'This turn is not cached.' }, ], prompt_caching: { enabled: true, ttl: '1h', cut_after_message_index: 1, }, }, { headers: { 'anthropic-beta': 'prompt-caching-2024-07-31' }, }, ); ``` `cut_after_message_index` is zero-based. If omitted, NanoGPT will select a cache boundary automatically; set it explicitly if you need full control. Switch back to explicit `cache_control` blocks if you need multiple cache breakpoints or mixed TTLs in the same payload. ### Explicit Prompt Cache Consistency NanoGPT automatically fails over to backup services when the primary service is temporarily unavailable. While this ensures high availability, it can break your prompt cache because **each backend service maintains its own separate cache**. If cache consistency is more important than availability for your use case, you can enable the `stickyProvider` option: ```json theme={null} { "model": "anthropic/claude-sonnet-4.6", "messages": [...], "prompt_caching": { "enabled": true, "ttl": "5m", "stickyProvider": true } } ``` **Behavior:** * **`stickyProvider: false` (default)** — If the primary service fails, NanoGPT automatically retries with a backup service. Your request succeeds, but the cache may be lost (you'll pay full price for that request and need to rebuild the cache). * **`stickyProvider: true`** — If the primary service fails, NanoGPT returns a 503 error instead of failing over. Your cache remains intact for when the service recovers. **When to use `stickyProvider: true`:** * You have very large cached contexts where cache misses are expensive * You prefer to retry failed requests yourself rather than pay for cache rebuilds * Cost predictability is more important than request success rate **When to use `stickyProvider: false` (default):** * You prefer requests to always succeed when possible * Occasional cache misses are acceptable * You're using shorter contexts where cache rebuilds are inexpensive **Error response when stickyProvider blocks a failover:** ```json theme={null} { "error": { "message": "Service is temporarily unavailable. Fallback disabled to preserve prompt cache consistency. Switching services would invalidate your cached tokens. Remove stickyProvider option or retry later.", "status": 503, "type": "service_unavailable", "code": "fallback_blocked_for_cache_consistency" } } ``` ### Troubleshooting * 400 unsupported image: ensure the image is a valid PNG/JPEG/WebP, not a tiny 1×1 pixel, and either HTTPS URL or a base64 data URL. * 503 after fallbacks: try a different model, verify API key/session, and prefer base64 data URL for local or protected assets. * Missing usage events: confirm `include_usage` is `true` in the payload or that prompt caching is enabled. ## Context Memory Enable unlimited-length conversations with lossless, hierarchical memory. * Append `:memory` to any model name * Or send header `memory: true` * Can be combined with web search: `:online:memory` * Retention: default 30 days; configure via `:memory-` (1..365) or header `memory_expiration_days: `; header takes precedence ```python Python theme={null} import requests BASE_URL = "https://nano-gpt.com/api/v1" API_KEY = "YOUR_API_KEY" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } # Suffix-based payload = { "model": "openai/gpt-5.6-sol:memory", "messages": [{"role": "user", "content": "Keep our previous discussion in mind and continue."}] } requests.post(f"{BASE_URL}/chat/completions", headers=headers, json=payload) ``` ```javascript JavaScript theme={null} const BASE_URL = "https://nano-gpt.com/api/v1"; const API_KEY = "YOUR_API_KEY"; // Header-based (with optional retention override) await fetch(`${BASE_URL}/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', 'memory': 'true', 'memory_expiration_days': '45' }, body: JSON.stringify({ model: 'openai/gpt-5.6-sol', messages: [{ role: 'user', content: 'Continue with full history awareness.' }] }) }); ``` ```bash cURL theme={null} # Combine with web search (and set retention to 90 days via suffix) curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol:online:memory-90", "messages": [ {"role": "user", "content": "Research and continue our plan without losing context."} ] }' # Header-based retention override (header takes precedence) curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "memory: true" \ -H "memory_expiration_days: 45" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [ {"role": "user", "content": "Use memory with 45-day retention."} ] }' ``` ### Custom Context Size Override When Context Memory is enabled, you can override the model-derived context size used for the memory compression step with `model_context_limit`. * Parameter: `model_context_limit` (number or numeric string) * Default: Derived from the selected model’s context size * Minimum: Values below 10,000 are clamped internally * Scope: Only affects memory compression; does not change the target model’s own window Examples: ```bash theme={null} # Enable memory via header; use model default context size curl -s -X POST \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "memory: true" \ https://nano-gpt.com/api/v1/chat/completions \ -d '{ "model": "google/gemini-3-flash-preview", "messages": [{"role":"user","content":"Briefly say hello."}], "stream": false }' # Explicit numeric override curl -s -X POST \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "memory: true" \ https://nano-gpt.com/api/v1/chat/completions \ -d '{ "model": "google/gemini-3-flash-preview", "messages": [{"role":"user","content":"Briefly say hello."}], "model_context_limit": 20000, "stream": false }' # String override (server coerces to number) curl -s -X POST \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "memory: true" \ https://nano-gpt.com/api/v1/chat/completions \ -d '{ "model": "google/gemini-3-flash-preview", "messages": [{"role":"user","content":"Briefly say hello."}], "model_context_limit": "30000", "stream": false }' ``` ## Reasoning Streams The Chat Completions endpoint separates the model’s visible answer from its internal reasoning. By default, reasoning is included and delivered alongside normal content so that clients can decide whether to display it. `:thinking` is model-specific and only works when that exact ID (or a documented alias) exists. `-thinking` is a legacy alias pattern for some model families only, not universal. Do not assume `-thinking` works for arbitrary model IDs. Always check `GET /api/v1/models` for exact valid IDs. See also: [Extended Thinking (Reasoning)](/api-reference/miscellaneous/extended-thinking). ### Endpoint variants Choose the base path that matches how your client consumes reasoning streams: * `https://nano-gpt.com/api/v1/chat/completions` — default endpoint that streams internal thoughts through `choices[0].delta.reasoning` (and repeats them in `message.reasoning` on completion). Recommended for apps like SillyTavern that understand the modern response shape. * `https://nano-gpt.com/api/v1legacy/chat/completions` — legacy contract that swaps the field name to `choices[0].delta.reasoning_content` / `message.reasoning_content` for older OpenAI-compatible clients. Use this for LiteLLM’s OpenAI adapter to avoid downstream parsing errors. * `https://nano-gpt.com/api/v1thinking/chat/completions` — reasoning-aware models write everything into the normal `choices[0].delta.content` stream so clients that ignore reasoning fields still see the full conversation transcript. This is the preferred base URL for JanitorAI. ### Streaming payload format Server-Sent Event (SSE) streams emit the answer in `choices[0].delta.content` and the thought process in `choices[0].delta.reasoning` (plus optional `delta.reasoning_details`). Reasoning deltas are dispatched before or alongside regular content, letting you render both panes in real-time. ```text theme={null} data: { "choices": [{ "delta": { "reasoning": "Assessing possible tool options…" } }] } data: { "choices": [{ "delta": { "content": "Let me walk you through the solution." } }] } ``` When streaming completes, the formatter aggregates the collected values and repeats them in the final payload: `choices[0].message.content` contains the assistant reply and `choices[0].message.reasoning` (plus `reasoning_details` when available) contains the full chain-of-thought. Non-streaming requests reuse the same formatter, so the reasoning block is present as a dedicated field. ### Showing or hiding reasoning Send `reasoning: { "exclude": true }` to strip the reasoning payload from both streaming deltas and the final message. With this flag set, `delta.reasoning` and `message.reasoning` are omitted entirely. ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-opus-4.6", "messages": [{"role": "user", "content": "What is 2+2?"}], "reasoning": {"exclude": true} }' ``` **Without reasoning.exclude**: ```json theme={null} { "choices": [{ "message": { "content": "The answer is 4.", "reasoning": "The user is asking for a simple addition. 2+2 equals 4." } }] } ``` **With reasoning.exclude**: ```json theme={null} { "choices": [{ "message": { "content": "The answer is 4." } }] } ``` ### Reasoning Effort `reasoning_effort` (or `reasoning.effort`) controls reasoning depth and also acts as an explicit reasoning-mode signal. Any value other than `"none"` is treated as a request to enable reasoning/thinking behavior. Use `"none"` to explicitly disable reasoning behavior. #### Parameter: `reasoning_effort` | Value | Description | | --------- | ----------------------------- | | `none` | Explicitly disables reasoning | | `minimal` | Lowest reasoning depth | | `low` | Low reasoning depth | | `medium` | Medium reasoning depth | | `high` | High reasoning depth | | `xhigh` | Maximum reasoning depth | #### Usage The `reasoning_effort` parameter can be passed at the top level: ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "anthropic/claude-opus-4.6", "messages": [ {"role": "user", "content": "Explain quantum entanglement step by step"} ], "reasoning_effort": "high", "max_tokens": 4096 }' ``` Alternatively, pass it as part of the `reasoning` object: ```json theme={null} { "model": "anthropic/claude-opus-4.6", "messages": [{"role": "user", "content": "Solve this complex math problem..."}], "reasoning": { "effort": "high" } } ``` Both formats are accepted. If both are present, top-level `reasoning_effort` is authoritative for Chat Completions request shaping. #### Combining effort with exclude `reasoning.exclude` controls output visibility only. It hides reasoning fields/blocks, but does not inherently disable reasoning compute. If an effort level is set to a non-`none` value, reasoning can still run while hidden. ```json theme={null} { "model": "anthropic/claude-opus-4.6", "messages": [{"role": "user", "content": "..."}], "reasoning": { "effort": "high", "exclude": true } } ``` ### Model suffix: `:reasoning-exclude` You can toggle the filter without altering your JSON body by appending `:reasoning-exclude` to the `model` name. * Equivalent to sending `{ "reasoning": { "exclude": true } }` * Only the `:reasoning-exclude` suffix is stripped before the request is routed; other suffixes remain active * Works for streaming and non-streaming responses on both Chat Completions and Text Completions ```json theme={null} { "model": "anthropic/claude-opus-4.6:reasoning-exclude", "messages": [{ "role": "user", "content": "What is 2+2?" }] } ``` #### Combine with other suffixes `:reasoning-exclude` composes safely with the other routing suffixes you already use: * `:thinking` (when that exact model ID exists). `-thinking` variants are legacy aliases for some families only. * `:online` and `:online/linkup-deep` * `:memory` and `:memory-` Examples: * `anthropic/claude-sonnet-4.6:thinking:8192:reasoning-exclude` * :online:reasoning-exclude * `anthropic/claude-opus-4.6:memory-30:online/linkup-deep:reasoning-exclude` * `zai-org/glm-5:fast:reasoning-exclude` * `zai-org/glm-5:cheap:reasoning-exclude` ### Legacy delta field compatibility Older clients that expect the legacy `reasoning_content` field can opt in per request. Set `reasoning.delta_field` to `"reasoning_content"`, or use the top-level shorthands `reasoning_delta_field` / `reasoning_content_compat` if updating nested objects is difficult. When the toggle is active, every streaming and non-streaming response exposes `reasoning_content` instead of `reasoning`, and the modern key is omitted. The compatibility pass is skipped if `reasoning.exclude` is `true`, because no reasoning payload is emitted. If you cannot change the request payload, target `https://nano-gpt.com/api/v1legacy/chat/completions` instead—the legacy endpoint keeps `reasoning_content` without extra flags. LiteLLM’s OpenAI adapter should point here to maintain compatibility. For clients that ignore reasoning-specific fields entirely, use `https://nano-gpt.com/api/v1thinking/chat/completions` so the full text appears in the standard content stream; this is the correct choice for JanitorAI. ```json theme={null} { "model": "openai/gpt-5.6-sol", "messages": [...], "reasoning": { "delta_field": "reasoning_content" } } ``` #### Notes and limitations * GPU-TEE models (`phala/*`) require byte-for-byte SSE passthrough for signature verification. For those models, streaming cannot be filtered; the suffix has no effect on the streaming bytes. * When assistant content is an array (e.g., vision/text parts), only text parts are filtered; images and tool/metadata content are untouched. ## Service tiers (flex and priority) Set `service_tier` to request a non-default capacity tier on providers that support service tiers: * `auto` or omitted: use NanoGPT's normal routing and the provider default. * `default`: request the provider's standard tier where the provider accepts an explicit default value. * `flex`: request lower-cost, variable-capacity processing where supported. * `priority`: request higher-cost priority processing where supported. Behavior notes: * When `service_tier` is `"flex"` or `"priority"`, NanoGPT prefers routing to providers that support the requested tier. * Service tier availability is model- and provider-specific. Model pages show which tiers are supported. * Not all providers support service tiers, so tiered requests may be routed differently than default requests. * Header provider overrides (like `X-Provider`) and explicit provider selection are honored for pricing and x402 estimates. * Provider-native web search can force routing; tier pricing follows that routing. * If you explicitly force a provider that does not support service tiers, the requested tier may be ignored by the upstream provider, or routing and pricing may differ from the default route. Billing note: * Flex requests are billed at flex rates where applicable. * Priority requests are billed at priority rates where applicable. * High-context pricing may also apply for models and providers with separate high-context SKUs, such as `es2k` pricing for GPT-5.5/GPT-5.4 where available. Response note: * Responses now include a top-level `service_tier` field when it is provided on the request. ### Example: flex tier ```json theme={null} { "model": "openai/gpt-5.5", "messages": [ { "role": "user", "content": "Give me a concise release note." } ], "service_tier": "flex" } ``` ### Example: priority tier ```json theme={null} { "model": "openai/gpt-5.5", "messages": [ { "role": "user", "content": "Give me a concise release note." } ], "service_tier": "priority" } ``` ## YouTube Transcripts Automatically fetch and prepend YouTube video transcripts when the latest user message contains YouTube links. ### Defaults * Parameter: `youtube_transcripts` (boolean) * Default: `false` (opt-in) * Opt-in: set `youtube_transcripts` to `true` (string `"true"` is also accepted) to fetch transcripts * Limit: Up to 3 YouTube URLs processed per request * Higher volume: Use the standalone [`POST /api/youtube-transcribe`](/api-reference/endpoint/youtube-transcribe) endpoint for up to 10 URLs per request * Injection: Transcripts are added as a system message before your messages * Billing: \$0.01 per transcript fetched ### Enable automatic transcripts By default, YouTube links are ignored. Set `youtube_transcripts` to `true` when you want the system to retrieve and bill for transcripts. ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "google/gemini-3-flash-preview", "messages": [ {"role": "user", "content": "Summarize this: https://youtu.be/dQw4w9WgXcQ"} ], "youtube_transcripts": true }' ``` ### Notes * Web scraping is separate. To scrape non‑YouTube URLs, set `scraping: true`. YouTube transcripts do not require `scraping: true`. * When not requested, YouTube links are ignored for transcript fetching and are not billed. * If your balance is insufficient when enabled, the request may be blocked with a 402. ## Performance Benchmarks LinkUp achieves state-of-the-art performance on OpenAI's SimpleQA benchmark: | Provider | Score | | ---------------------- | ------ | | LinkUp Deep Search | 90.10% | | Exa | 90.04% | | Perplexity Sonar Pro | 86% | | LinkUp Standard Search | 85% | | Perplexity Sonar | 77% | | Tavily | 73% | ## Compressed Request Bodies Request bodies may be sent gzip, deflate, or Brotli compressed by adding a `Content-Encoding` header (e.g. `Content-Encoding: gzip`). Conversation-history payloads compress roughly 5:1, which cuts upload time — and therefore time-to-first-token — on long conversations. Requires a JSON `Content-Type` and authentication; the 32 MB body limit applies to both the compressed and decompressed body. See [Compressed Request Bodies](/api-reference/miscellaneous/request-compression) for SDK-ready snippets. ## Important Notes * Web search increases input token count, which affects total cost * Models gain access to real-time information published less than a minute ago * Internet connectivity can provide up to 10x improvement in factuality * All models support web search - append a suffix or send a `webSearch` object (`linkup` is supported as an alias) # Check Balance Source: https://docs.nano-gpt.com/api-reference/endpoint/check-balance POST /check-balance Check the account balance # Retrieve Midjourney Generation Status Source: https://docs.nano-gpt.com/api-reference/endpoint/check-midjourney-status POST /check-midjourney-status Check the status of an asynchronous Midjourney image generation task # Completions Source: https://docs.nano-gpt.com/api-reference/endpoint/completion POST /v1/completions Creates a completion for the provided prompt. This endpoint is available on a best-effort basis for legacy compatibility, and performance may be less consistent than /v1/chat/completions because not all upstream providers support the legacy completions API. `POST /api/v1/completions` is available on a best-effort basis for legacy OpenAI compatibility. Performance and behavior may be less consistent than `POST /api/v1/chat/completions` because not all upstream providers support the legacy completions API. `POST /api/v1/completions` supports the same model suffix parser for provider routing (`:fast`, `:cheap`, etc.) and `:reasoning-exclude`, but this endpoint remains best-effort legacy compatibility. Prefer Chat Completions for new integrations. See [Model Suffixes](/api-reference/miscellaneous/model-suffixes). The body `provider` field accepts either the existing provider ID string or a structured routing object with fields such as `order`, `only`, `ignore`, `sort`, `max_price`, `allow_fallbacks`, and `require_parameters`. See [Provider Selection > Provider Routing Object](/api-reference/miscellaneous/provider-selection#provider-routing-object). # Deposits (Crypto + Fiat) Source: https://docs.nano-gpt.com/api-reference/endpoint/crypto-deposits POST https://nano-gpt.com/api/transaction/create/{ticker} Create deposit payment intents and track status programmatically ## Overview NanoGPT supports multiple deposit methods (crypto, stablecoins, and cards). This page documents the public endpoints you can use to: * Create a deposit payment intent * Check deposit limits * Track deposit status (polling or SSE) Deposits are credited automatically after payment confirmation. ## Supported Payment Methods Limits vary by method and can change. Use `GET /api/transaction/limits/{ticker}` for the authoritative min/max. ### Crypto (invoice / address) | Ticker | Name | Provider | | ---------- | ----------------- | ---------------- | | `BTC` | Bitcoin | BTCPay | | `BTC-LN` | Bitcoin Lightning | BTCPay | | `LTC` | Litecoin | BTCPay | | `LTC-MWEB` | Litecoin MWEB | BTCPay | | `XMR` | Monero | BTCPay | | `DOGE` | Dogecoin | BTCPay | | `DASH` | Dash | BTCPay | | `ZEC` | Zcash | BTCPay | | `BCH` | Bitcoin Cash | Prompt.Cash | | `BAN` | Banano | Nanswap (legacy) | | `KAS` | Kaspa | Nanswap | | `TON` | Toncoin | Nanswap | | `NEAR` | NEAR Protocol | Nanswap | | `EGLD` | MultiversX | Nanswap | | `VVV` | VVV | Nanswap | | `ZANO` | Zano | Zano proxy | | `FUSD` | Freedom Dollar | Zano proxy | ### Stablecoins and multi-chain (Daimo Pay) These methods settle as USDC and can be paid from multiple chains/wallets. | Ticker | Name | | ------ | -------- | | `USDC` | USD Coin | | `USDT` | Tether | | `ETH` | Ethereum | | `SOL` | Solana | ### Fiat (card) | Ticker | Name | Provider | | ------ | ------------- | -------- | | `USD` | US Dollar | Stripe | | `EUR` | Euro | Stripe | | `GBP` | British Pound | Stripe | ### Nano (direct deposit) Nano deposits use a direct-send flow to your assigned Nano deposit address (no invoice creation). See: [Check Balance](/api-reference/endpoint/check-balance) and [Receive Nano](/api-reference/endpoint/receive-nano). ## Authentication All `/api/transaction/*` endpoints require API key authentication using one of these methods: ```bash theme={null} # Method 1: Authorization header curl -H "Authorization: Bearer YOUR_API_KEY" # Method 2: x-api-key header curl -H "x-api-key: YOUR_API_KEY" ``` ## Endpoint Summary Pick the endpoint based on the payment method: * Crypto invoice / swap deposits: `POST /api/transaction/create/{ticker}` * Daimo Pay (multi-chain): `POST /api/transaction/create/daimo/{ticker}` * Card (Stripe): `POST /api/transaction/create/usd` * Limits: `GET /api/transaction/limits/{ticker}` * Status polling: `GET /api/transaction/status/{ticker}/{txId}` * Status SSE: `GET /api/transaction/status/events?ticker={ticker}&txId={txId}` * Nano (XNO): send to `nanoDepositAddress` (from [Check Balance](/api-reference/endpoint/check-balance)); optionally call [Receive Nano](/api-reference/endpoint/receive-nano) Create an invoice / pay-in address for supported crypto tickers Ticker symbol. Examples: `btc`, `btc-ln`, `ltc`, `zec`, `bch`, `kas`, `vvv`, `zano`, `fusd` Amount of cryptocurrency to deposit. Must be between minimum and maximum limits. ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/transaction/create/btc \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"amount": 0.001}' ``` ```javascript JavaScript theme={null} const response = await fetch('https://nano-gpt.com/api/transaction/create/btc', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 0.001 }) }); const deposit = await response.json(); console.log('Send BTC to:', deposit.address); ``` ```javascript BTC Lightning Example theme={null} // BTC-LN has lower minimum ($0.10) const lnResponse = await fetch('https://nano-gpt.com/api/transaction/create/btc-ln', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 0.00001 }) }); const lnDeposit = await lnResponse.json(); console.log('Lightning invoice:', lnDeposit.paymentLink); ``` ```json Success Response theme={null} { "txId": "abc123", "address": "bc1q...", "amount": 0.001, "status": "New", "paymentLink": "bitcoin:bc1q...?amount=0.001", "createdAt": "2026-01-19T12:00:00.000Z", "expiresAt": "2026-01-19T13:00:00.000Z" } ``` ### Response Fields Unique transaction identifier for tracking Deposit address for sending crypto Requested deposit amount Payment status. Common values include: `New`, `Pending`, `Processing`, `Paid`, `Completed`, `Expired`, `Failed` URI for wallet apps (e.g., `bitcoin:address`) ISO timestamp of creation ISO timestamp when address expires Get minimum and maximum deposit amounts for a payment method Payment method ticker symbol (for example: `btc`, `btc-ln`, `zec`, `usdc`, `sol`, `usd`) ```bash cURL theme={null} curl https://nano-gpt.com/api/transaction/limits/kas \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript JavaScript theme={null} const limitsResponse = await fetch('https://nano-gpt.com/api/transaction/limits/btc', { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }); const limits = await limitsResponse.json(); // Validate amount before creating deposit if (amount < limits.minimum) { throw new Error(`Minimum deposit is ${limits.minimum} BTC`); } ``` ```json Limits Response theme={null} { "minimum": 10.5, "maximum": 5250, "fiatEquivalentMinimum": 1, "fiatEquivalentMaximum": 500, "timestamp": 1705669200 } ``` Create a multi-chain payment (USDC/USDT/ETH/SOL) via Daimo Pay Daimo payment ticker. Common values: `usdc`, `usdt`, `eth`, `sol` Amount to pay. This is typically treated as a USD/USDC amount (for example, `10` for about \$10). ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/transaction/create/daimo/usdc \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"amount": 10}' ``` ```json Success Response theme={null} { "paymentId": "encrypted_payment_id" } ``` Create a Stripe Checkout session for card deposits Amount in the selected fiat currency (for example, `10`). ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/transaction/create/usd \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"amount": 10}' ``` ```json Success Response theme={null} { "paymentLink": "https://checkout.stripe.com/c/pay/cs_test_..." } ``` ## Status Tracking Most deposit methods can be tracked by polling a status endpoint: ```http theme={null} GET /api/transaction/status/{ticker}/{txId} ``` Some methods use provider-specific IDs (for example Stripe session IDs). The create response usually returns the correct `txId` value to use. Example: ```bash theme={null} curl "https://nano-gpt.com/api/transaction/status/btc/abc123" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Real-time status (SSE) Instead of polling, you can subscribe to Server-Sent Events: ```http theme={null} GET /api/transaction/status/events?ticker={ticker}&txId={txId} ``` Example: ```bash theme={null} curl -N "https://nano-gpt.com/api/transaction/status/events?ticker=btc&txId=abc123" \ -H "Authorization: Bearer YOUR_API_KEY" ``` The server may also return an `x-poll-after` header on status responses to indicate a recommended retry interval (seconds). ## Transaction Lifecycle Status values can vary slightly by payment method, but a typical lifecycle looks like: | Status | Meaning | | ----------------- | ---------------------------------------------------------------------- | | `New` / `Pending` | Deposit created, awaiting payment | | `Processing` | Payment detected and being confirmed/processed | | `Paid` | Payment confirmed, crediting pending | | `Completed` | Balance credited | | `Expired` | Payment window expired (some providers can still credit late payments) | | `Failed` | Provider error or processing failure | Notes: * `BTC` deposits can take longer to reach `Completed` due to on-chain settlement/confirmations. * Some providers may accept payment after an invoice expires; in those cases the deposit can still complete. ## Payment Providers NanoGPT uses different providers per ticker: * **BTCPay**: BTC, BTC-LN, LTC, LTC-MWEB, XMR, DOGE, DASH, ZEC * **Prompt.Cash**: BCH * **Nanswap**: BAN, KAS, TON, NEAR, EGLD, VVV * **Zano proxy**: ZANO, FUSD * **Daimo Pay**: USDC, USDT, ETH, SOL (multi-chain payments settling as USDC) * **Stripe**: card deposits (USD/EUR/GBP) * **Native Nano**: XNO direct deposits to your `nanoDepositAddress` ## Pricing Endpoints These endpoints are commonly used by deposit flows and checkout UIs: * `GET /api/get-nano-price` (NANO/USD pricing) * `GET /api/get-fiat-prices` (fiat FX rates) Example: ```bash theme={null} curl "https://nano-gpt.com/api/get-nano-price" curl "https://nano-gpt.com/api/get-fiat-prices" ``` Example responses: ```json theme={null} // GET /api/get-nano-price { "pair": "NANOUSD", "latestPrice": 1.23 } ``` ```json theme={null} // GET /api/get-fiat-prices { "usdTo": { "USD": 1, "EUR": 0.92, "GBP": 0.79 }, "currencies": { "USD": "United States Dollar", "EUR": "Euro", "GBP": "British Pound" } } ``` ## Bonuses and Discounts * `BTC-LN`: Lightning deposits may include a bonus on the credited amount (if enabled). * `XNO`: Paying with Nano balance may include a usage discount (if enabled). * `USDC` via Daimo: USDC stablecoin deposits typically credit close to \$1 per 1 USDC. ## Error Handling ### HTTP Status Codes | Code | Description | | ---- | --------------------------------------------------------- | | 200 | Success | | 400 | Invalid amount, unsupported ticker, or below/above limits | | 401 | Authentication failure | | 429 | Rate limited | | 500 | Provider unavailable or internal error | ### Common Error Messages * `"No amount specified"` - Missing amount in request body * `"Invalid amount. Must be a positive number."` - Amount validation failed * `"Minimum amount is X"` - Below minimum threshold * `"Maximum amount is X"` - Above maximum threshold * `"Unsupported ticker"` - Ticker not supported * `"This payment method is currently not available"` - Provider temporarily unavailable ## Rate Limits * **10 requests per 10 minutes** per IP address or API key * Rate limit applies to all deposit creation endpoints ## Payment Flow Call `/api/transaction/create/{ticker}` with desired amount Extract `address` from response User sends crypto to the provided address Account balance automatically updated when payment confirms # Data API Source: https://docs.nano-gpt.com/api-reference/endpoint/data-api GET https://nano-gpt.com/api/v1/data Discover and call NanoGPT data tools through one stable endpoint family ## Overview NanoGPT exposes a unified Data API under `/api/v1/data`. It gives customers one discoverable entry point for web search, URL scraping, maps data, social data, and business enrichment. The Data API is a wrapper around the existing direct endpoints. It does not change the underlying response bodies, validation rules, billing, rate limits, async behavior, provider options, authentication, or x402 behavior unless noted here. Direct endpoints remain available for compatibility. ## Base URL ```text theme={null} https://nano-gpt.com/api/v1/data ``` Local development: ```text theme={null} http://localhost:3000/api/v1/data ``` ## Authentication Use the same NanoGPT API key authentication as existing API routes: ```http theme={null} Authorization: Bearer YOUR_API_KEY ``` The public accountless x402 contract is available on selected Data API paths. To request an accountless x402 quote, send the API request without `Authorization` or `x-api-key`, and include `x-x402: true`. NanoGPT will return `402 Payment Required` with available payment options. An unauthenticated request without `x-x402: true` is treated as a normal unauthenticated request and returns `401 missing_api_key`. Currently documented accountless Data API paths: * `POST /api/v1/data/web/search` * `POST /api/v1/data/url/scrape` Use `GET /api/v1/x402/endpoints` as the deployment source of truth, including Lightning L402 availability when advertised. See [Accountless x402 API Payments](/api-reference/miscellaneous/x402) for completion, replay, and polling behavior. Direct legacy routes remain available, but accountless docs prefer the public v1 data paths. Some underlying endpoints may also support browser-session flows. The Data API forwards authentication and payment-related headers to the underlying route. ## Discovery List available Data API endpoints: ```http theme={null} GET /api/v1/data ``` Alias: ```http theme={null} GET /api/v1/data/endpoints ``` The catalog response is cacheable: ```http theme={null} Cache-Control: public, max-age=300, stale-while-revalidate=3600 ``` ### Example catalog response ```json theme={null} { "object": "list", "data": [ { "id": "web.search", "object": "data.endpoint", "name": "Web Search", "description": "Search the web through NanoGPT search providers.", "category": "search", "provider": "NanoGPT", "methods": ["POST"], "endpoint": "/api/v1/data/web/search", "url": "https://nano-gpt.com/api/v1/data/web/search", "direct_endpoint": "/api/web", "pricing": "Usage-based; estimated from provider, depth, and partner markup.", "status": "available" } ], "meta": { "count": 20, "providers": ["Apify", "Firecrawl", "Hunter", "NanoGPT"], "categories": ["business", "maps", "scraping", "search", "social"], "dispatch": { "endpoint": "/api/v1/data", "body": { "endpoint": "web.search", "input": { "query": "latest AI news" } } } } } ``` ## Endpoint Metadata For endpoints that do not support `GET`, calling their Data API path with `GET` returns the public metadata object instead of dispatching the tool. ```http theme={null} GET /api/v1/data/web/search ``` For endpoints that do support `GET`, the request is forwarded to the underlying endpoint. ## Calling Endpoints The Data API supports path-style dispatch and body-style dispatch. ### Path-style dispatch Use a stable Data API path and send the underlying endpoint's normal input body. ```bash theme={null} curl https://nano-gpt.com/api/v1/data/web/search \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "latest AI model releases", "depth": "standard" }' ``` Path-style dispatch is the simplest format for fixed integrations because each tool has a URL. ### Body-style dispatch Use `POST /api/v1/data` and specify the target endpoint in the request body. ```bash theme={null} curl https://nano-gpt.com/api/v1/data \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "endpoint": "web.search", "input": { "query": "latest AI model releases", "depth": "standard" } }' ``` The dispatch key can be `endpoint`, `id`, or `type`. Endpoint input can be provided in an `input` object: ```json theme={null} { "endpoint": "web.search", "input": { "query": "latest AI model releases" } } ``` It can also be provided directly in the dispatch body: ```json theme={null} { "endpoint": "web.search", "query": "latest AI model releases" } ``` If `input` is present, it is forwarded. If `data` is present and `input` is absent, `data` is forwarded. Otherwise, all fields except `endpoint`, `id`, `type`, `input`, and `data` are forwarded as the endpoint input. ## Request Forwarding Behavior The Data API: * Preserves request query parameters. * Preserves request headers except hop-by-hop headers such as `connection`, `content-length`, `host`, `keep-alive`, `transfer-encoding`, and `upgrade`. * Forces `Content-Type: application/json` when it forwards a JSON body. * Forwards JSON request bodies to `POST` routes. * Allows empty-body path-style `POST` requests and forwards `{}`. * Requires a valid JSON body for body-style `POST /api/v1/data`. * Adds tracking headers to dispatched responses. ## Response Headers Dispatched responses include: ```http theme={null} x-nanogpt-data-endpoint: web.search x-nanogpt-direct-endpoint: /api/web ``` These headers are exposed to browser clients through: ```http theme={null} Access-Control-Expose-Headers: x-nanogpt-data-endpoint, x-nanogpt-direct-endpoint ``` Use these headers for client-side observability, debugging, analytics, and support traces. ## Error Format Errors generated by the Data API wrapper use this shape: ```json theme={null} { "error": { "message": "Unknown data endpoint: not.real", "type": "invalid_request_error", "code": "unknown_endpoint" } } ``` Common wrapper-level errors: | Status | Code | Meaning | | ------ | --------------------------- | ----------------------------------------------------------------- | | `400` | `invalid_json` | Request body is missing or invalid JSON where JSON is required. | | `400` | `invalid_body` | Body-style dispatch body is not an object. | | `400` | `missing_endpoint` | Body-style dispatch did not include `endpoint`, `id`, or `type`. | | `404` | `unknown_endpoint` | Endpoint id or path is not recognized. | | `405` | `method_not_allowed` | Endpoint exists but does not support the requested method. | | `500` | `unsupported_data_endpoint` | Catalog entry exists but no dispatcher implementation matched it. | Once a request is forwarded, the underlying endpoint may return its own existing error format. ## Endpoint Catalog | Endpoint ID | Data API Path | Direct Endpoint | Methods | Category | Provider | Pricing Note | | ---------------------------- | ------------------------------------ | ------------------------------- | ------------- | -------- | --------- | ---------------------------------------------------------------- | | `web.search` | `/api/v1/data/web/search` | `/api/web` | `POST` | search | NanoGPT | Usage-based; estimated from provider, depth, and partner markup. | | `url.scrape` | `/api/v1/data/url/scrape` | `/api/scrape-urls` | `POST` | scraping | NanoGPT | Per URL, with higher pricing for stealth mode. | | `firecrawl` | `/api/v1/data/firecrawl` | `/api/v1/firecrawl` | `POST` | scraping | Firecrawl | Credit-based Firecrawl pricing with NanoGPT markup. | | `google_maps.search` | `/api/v1/data/google-maps/search` | `/api/v1/googlemaps` | `GET`, `POST` | maps | Apify | Apify charged-event pricing with NanoGPT markup. | | `google_maps.reviews` | `/api/v1/data/google-maps/reviews` | `/api/v1/googlemaps/reviews` | `POST` | maps | Apify | Apify charged-event pricing with NanoGPT markup. | | `reddit.scrape` | `/api/v1/data/reddit` | `/api/v1/reddit` | `POST` | social | Apify | Apify charged-event pricing with NanoGPT markup. | | `linkedin.profile` | `/api/v1/data/linkedin/profile` | `/api/v1/linkedin/profile` | `POST` | business | Apify | Apify charged-event pricing with NanoGPT markup. | | `instagram.profile` | `/api/v1/data/instagram/profile` | `/api/v1/instagram/profile` | `POST` | social | Apify | Apify charged-event pricing with NanoGPT markup. | | `instagram.posts` | `/api/v1/data/instagram/posts` | `/api/v1/instagram/posts` | `POST` | social | Apify | Apify charged-event pricing with NanoGPT markup. | | `instagram.reels` | `/api/v1/data/instagram/reels` | `/api/v1/instagram/reels` | `POST` | social | Apify | Apify charged-event pricing with NanoGPT markup. | | `tiktok.scrape` | `/api/v1/data/tiktok` | `/api/v1/tiktok` | `POST` | social | Apify | Apify charged-event pricing with NanoGPT markup. | | `x.read` | `/api/v1/data/x` | `/api/v1/x` | `GET`, `POST` | social | NanoGPT | Endpoint-specific usage pricing. | | `hunter.discover` | `/api/v1/data/hunter/discover` | `/api/v1/hunter/discover` | `POST` | business | Hunter | Free. | | `hunter.domain_search` | `/api/v1/data/hunter/domain-search` | `/api/v1/hunter/domain-search` | `GET`, `POST` | business | Hunter | Hunter search-credit pricing with NanoGPT markup. | | `hunter.email_finder` | `/api/v1/data/hunter/email-finder` | `/api/v1/hunter/email-finder` | `GET`, `POST` | business | Hunter | Hunter search-credit pricing with NanoGPT markup. | | `hunter.email_verifier` | `/api/v1/data/hunter/email-verifier` | `/api/v1/hunter/email-verifier` | `GET`, `POST` | business | Hunter | Hunter verification-credit pricing with NanoGPT markup. | | `hunter.person_enrichment` | `/api/v1/data/hunter/people/find` | `/api/v1/hunter/people/find` | `GET`, `POST` | business | Hunter | Hunter enrichment-credit pricing with NanoGPT markup. | | `hunter.company_enrichment` | `/api/v1/data/hunter/companies/find` | `/api/v1/hunter/companies/find` | `GET`, `POST` | business | Hunter | Hunter enrichment-credit pricing with NanoGPT markup. | | `hunter.combined_enrichment` | `/api/v1/data/hunter/combined/find` | `/api/v1/hunter/combined/find` | `GET`, `POST` | business | Hunter | Hunter enrichment-credit pricing with NanoGPT markup. | | `hunter.email_count` | `/api/v1/data/hunter/email-count` | `/api/v1/hunter/email-count` | `GET`, `POST` | business | Hunter | Free. | ## Endpoint Aliases Body-style dispatch accepts canonical endpoint IDs and these aliases: | Alias | Resolves To | | ----------------------- | ---------------------------- | | `googlemaps` | `google_maps.search` | | `googlemaps.search` | `google_maps.search` | | `google-maps.search` | `google_maps.search` | | `maps.search` | `google_maps.search` | | `maps.reviews` | `google_maps.reviews` | | `linkedin` | `linkedin.profile` | | `linked-in.profile` | `linkedin.profile` | | `ig.profile` | `instagram.profile` | | `ig.posts` | `instagram.posts` | | `ig.reels` | `instagram.reels` | | `twitter.read` | `x.read` | | `twitter` | `x.read` | | `x` | `x.read` | | `search` | `web.search` | | `web` | `web.search` | | `scrape` | `url.scrape` | | `urls.scrape` | `url.scrape` | | `url_scrape` | `url.scrape` | | `hunter.domain-search` | `hunter.domain_search` | | `hunter.email-finder` | `hunter.email_finder` | | `hunter.email-verifier` | `hunter.email_verifier` | | `hunter.people.find` | `hunter.person_enrichment` | | `hunter.person` | `hunter.person_enrichment` | | `hunter.company` | `hunter.company_enrichment` | | `hunter.companies.find` | `hunter.company_enrichment` | | `hunter.combined.find` | `hunter.combined_enrichment` | | `hunter.email-count` | `hunter.email_count` | The resolver also normalizes endpoint IDs by trimming slashes, accepting `api/v1/data/...`-style strings, converting path slashes to dots, and converting hyphens to underscores. ## X/Twitter Subpaths `x.read` is special. The catalog lists the base X endpoint: ```text theme={null} /api/v1/data/x ``` The dispatcher also supports dynamic X subpaths and forwards them to `/api/v1/x/...`. Examples: ```http theme={null} GET /api/v1/data/x/search?query=nanogpt POST /api/v1/data/x/search?query=nanogpt GET /api/v1/data/x/user?username=nanogpt GET /api/v1/data/x/tweet?id=1234567890 ``` For path-style `POST` requests with no body, the wrapper forwards `{}` and preserves the query string. This supports query-only X calls. ## Examples ### Web Search ```bash theme={null} curl https://nano-gpt.com/api/v1/data/web/search \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "NanoGPT API data endpoints", "provider": "linkup", "depth": "standard", "outputType": "searchResults" }' ``` Body-style dispatch: ```bash theme={null} curl https://nano-gpt.com/api/v1/data \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "endpoint": "web.search", "input": { "query": "NanoGPT API data endpoints", "provider": "linkup", "depth": "standard", "outputType": "searchResults" } }' ``` ### Scrape URLs ```bash theme={null} curl https://nano-gpt.com/api/v1/data/url/scrape \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "urls": ["https://nano-gpt.com"], "stealthMode": false }' ``` ### Accountless x402 Data Request ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/web/search \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "query": "NanoGPT API data endpoints", "provider": "linkup", "depth": "standard", "outputType": "searchResults" }' ``` ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/url/scrape \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "urls": ["https://nano-gpt.com"], "stealthMode": false }' ``` Both examples return `402 Payment Required` with a `payment` object when accountless x402 is enabled and the request can be quoted. If you receive `401 missing_api_key` immediately, check that the initial quote request includes `x-x402: true`. Without that header, NanoGPT does not enter the x402 quote flow. ### Google Maps Search ```bash theme={null} curl https://nano-gpt.com/api/v1/data/google-maps/search \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "searchStringsArray": ["coffee shops in Amsterdam"], "maxCrawledPlacesPerSearch": 10 }' ``` Google Maps search also supports `GET` for the same behavior exposed by the direct `/api/v1/googlemaps` endpoint, including polling async runs when applicable. ### Hunter Domain Search ```bash theme={null} curl "https://nano-gpt.com/api/v1/data/hunter/domain-search?domain=example.com" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ```bash theme={null} curl https://nano-gpt.com/api/v1/data/hunter/domain-search \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "domain": "example.com" }' ``` Body-style dispatch: ```bash theme={null} curl https://nano-gpt.com/api/v1/data \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "endpoint": "hunter.domain_search", "input": { "domain": "example.com" } }' ``` ### X Search With Query Parameters ```bash theme={null} curl "https://nano-gpt.com/api/v1/data/x/search?query=nanogpt" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` Path-style `POST` also works for query-only X calls: ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/data/x/search?query=nanogpt" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ## Related Docs * [Direct Web Search API](/api-reference/endpoint/web-search) * [Web Scraping](/api-reference/endpoint/scrape-urls) * [Data Extraction APIs](/api-reference/endpoint/data-extraction) * [x402 Payments](/api-reference/miscellaneous/x402) # Data Extraction APIs Source: https://docs.nano-gpt.com/api-reference/endpoint/data-extraction POST https://nano-gpt.com/api/v1/firecrawl Public data extraction endpoints for web, maps, social, and email/domain intelligence workflows ## Overview NanoGPT exposes data extraction APIs for web, maps, social, and email/domain intelligence workflows. For new integrations that need a stable, discoverable entry point across these tools, use the unified [Data API](/api-reference/endpoint/data-api) under `/api/v1/data`. It wraps the direct endpoints documented here without changing their response bodies, billing, validation rules, async behavior, or provider-specific options. These endpoints require API key authentication. Each request estimates a maximum charge before running. Some endpoints require `maxTotalChargeUsd` when the result count is unbounded. ## Authentication Use either header: * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Shared Billing and Runtime Behavior * Responses include returned data plus NanoGPT billing metadata. * Partial results may still be billed if the extraction job produced usable data before failing. * Result limits exist, commonly up to 5,000 returned items for social and data scrapers. * Long-running calls can take up to several minutes. * For unbounded searches, set `maxTotalChargeUsd` to cap spend. * Use `resultLimit`, `resultsLimit`, or endpoint-specific max-count fields to keep runs bounded. Typical responses include a data payload and billing metadata: ```json theme={null} { "data": [], "meta": { "nanogpt": { "costUsd": 0.05, "itemsReturned": 25 } } } ``` ## Endpoints | Data API Path | Direct Endpoint | Method | Purpose | | ---------------------------------- | ---------------------------- | --------- | ----------------------------------------------------------- | | `/api/v1/data/firecrawl` | `/api/v1/firecrawl` | POST | Web page scraping, site maps, and crawls. | | `/api/v1/data/google-maps/search` | `/api/v1/googlemaps` | GET, POST | Google Maps place search and detail extraction. | | `/api/v1/data/google-maps/reviews` | `/api/v1/googlemaps/reviews` | POST | Google Maps review extraction. | | `/api/v1/data/instagram/profile` | `/api/v1/instagram/profile` | POST | Instagram profile extraction. | | `/api/v1/data/instagram/posts` | `/api/v1/instagram/posts` | POST | Instagram post extraction. | | `/api/v1/data/instagram/reels` | `/api/v1/instagram/reels` | POST | Instagram Reels extraction. | | `/api/v1/data/reddit` | `/api/v1/reddit` | POST | Reddit post, comment, community, and user extraction. | | `/api/v1/data/tiktok` | `/api/v1/tiktok` | POST | TikTok hashtag, profile, search, and post extraction. | | `/api/v1/data/linkedin/profile` | `/api/v1/linkedin/profile` | POST | LinkedIn profile enrichment. | | `/api/v1/data/hunter/{path}` | `/api/v1/hunter/{path}` | GET, POST | Email/domain intelligence pass-through with path selection. | | Not in Data API catalog | `/api/v1/facebook/ads` | POST | Facebook/Meta ad library extraction. | The direct endpoints remain available. Use the Data API paths when you want endpoint discovery, stable public tool IDs, body-style dispatch for dynamic clients, or response headers identifying both the Data API endpoint and direct endpoint. ## Web Crawling ```text theme={null} POST https://nano-gpt.com/api/v1/firecrawl ``` ### Body ```json theme={null} { "operation": "scrape", "url": "https://example.com", "formats": ["markdown"], "waitForFinishSecs": 120 } ``` ### Operations * `scrape` * `map` * `crawl` ### Important Parameters | Parameter | Description | | ------------------- | --------------------------------------------------------------------------- | | `url` | Required target URL. | | `operation` | Operation to run. Must be one of `scrape`, `map`, or `crawl` when provided. | | `limit` | Page or item limit for `map` and `crawl`. | | `search` | Optional search string for `map`. | | `maxReturnedPages` | Maximum pages returned for `crawl`. | | `waitForFinishSecs` | Wait time, capped at 240 seconds. | PDF parsing has max page controls when PDF parsing is enabled. ## Google Maps ```text theme={null} POST https://nano-gpt.com/api/v1/googlemaps ``` ### Required Input Provide at least one of: * `searchStringsArray` * `startUrls` * `placeIds` * scrape-all-places option, when supported ### Important Parameters * `maxTotalChargeUsd` * `resultLimit` * `maxCrawledPlacesPerSearch` * `scrapePlaceDetailPage` * `scrapeContacts` * `scrapeSocialMediaProfiles` * `maxReviews` * `maxImages` * filters such as category, rating, website availability, and closed-place handling when supported ## Google Maps Reviews ```text theme={null} POST https://nano-gpt.com/api/v1/googlemaps/reviews ``` ### Required Input Provide at least one of: * `startUrls` * `placeIds` ### Important Parameters * `maxReviews` * `reviewsSort` * `reviewsStartDate` * `maxTotalChargeUsd` * `resultLimit` If you use `reviewsStartDate`, sort reviews by newest first. ## Facebook Ads ```text theme={null} POST https://nano-gpt.com/api/v1/facebook/ads ``` ### Required Input * `startUrls`: at least one Facebook Page URL or Meta Ad Library URL. ### Important Parameters * `resultsLimit` * `activeStatus`: `active` or `inactive` * `maxTotalChargeUsd` * `resultLimit` ## Instagram Profile ```text theme={null} POST https://nano-gpt.com/api/v1/instagram/profile ``` ### Required Input * `username` or `usernames`: Instagram username, profile URL, or profile ID. ### Important Parameters * `includeAboutSection` * `maxTotalChargeUsd` * `resultLimit` ## Instagram Posts ```text theme={null} POST https://nano-gpt.com/api/v1/instagram/posts ``` ### Required Input * `username`: one or more Instagram usernames, profile URLs, or post URLs. ### Important Parameters * `resultsLimit` * `dataDetailLevel`: `basicData` or `detailedData` * `skipPinnedPosts` * `maxTotalChargeUsd` * `resultLimit` ## Reddit ```text theme={null} POST https://nano-gpt.com/api/v1/reddit ``` ### Required Input Provide at least one of: * `startUrls`: Reddit URLs from `reddit.com` or `redd.it` * `searches`: search terms ### Important Parameters * `maxItems` * `maxPostCount` * `maxComments` * `maxCommunities` * `maxUsers` * `searchPosts` * `searchComments` * `searchCommunities` * `searchUsers` * `maxTotalChargeUsd` * `resultLimit` ## TikTok ```text theme={null} POST https://nano-gpt.com/api/v1/tiktok ``` ### Required Input Provide at least one of: * `hashtags` * `profiles` * `searchQueries` * `postURLs` ### Important Parameters * `resultsPerPage` * `maxProfilesPerQuery` * `searchSection` * transcription options, if exposed * `maxTotalChargeUsd` * `resultLimit` ## Hunter Hunter endpoints provide pass-through style email/domain intelligence with NanoGPT billing. Users authenticate with NanoGPT; do not pass a separate Hunter API key. Supported examples include: * `domain-search` * `email-finder` * `email-verifier` * `discover` ### Endpoints ```text theme={null} GET https://nano-gpt.com/api/v1/hunter/{path} POST https://nano-gpt.com/api/v1/hunter/{path} GET https://nano-gpt.com/api/v1/hunter?endpoint=domain-search POST https://nano-gpt.com/api/v1/hunter ``` For `GET`, query params are forwarded except: * `api_key` * `apiKey` * `endpoint` * `path` For `POST`, JSON body params are forwarded. Use `endpoint` or `path` to select the target path. A nested `params` object is also supported. ### Response Shape ```json theme={null} { "data": {}, "meta": { "nanogpt": { "endpoint": "domain-search", "hunterSearchesBilled": 1, "hunterVerificationsBilled": 0, "hunterEnrichmentsBilled": 0, "costUsd": 0.01 } } } ``` ## Example ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/googlemaps \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "searchStringsArray": ["coffee shops in Amsterdam"], "resultLimit": 25, "maxTotalChargeUsd": 1.00, "scrapePlaceDetailPage": true }' ``` # Embedding Models Source: https://docs.nano-gpt.com/api-reference/endpoint/embedding-models GET /v1/embedding-models List all available embedding models with detailed information ## Overview The `/api/v1/embedding-models` endpoint provides a comprehensive list of available embedding models with detailed information including dimensions, max dimensions, token limits, pricing, and feature support. Use this endpoint instead of hardcoding embedding model capabilities. This endpoint is cacheable. Refresh it periodically because model availability can change. ## Authentication Authentication is optional but may enable user-specific features: | Header | Format | Required | Description | | --------------- | ------------------ | -------- | -------------------------------- | | `Authorization` | `Bearer {api_key}` | Optional | API key for authenticated access | | `x-api-key` | `{api_key}` | Optional | Alternative API key header | ## Response Format Returns a list of all available embedding models with comprehensive details: ```json theme={null} { "object": "list", "data": [ { "id": "text-embedding-3-small", "object": "model", "created": 1754480583, "owned_by": "openai", "name": "Text Embedding 3 Small", "description": "Most cost-effective OpenAI embedding model with dimension reduction support", "dimensions": 1536, "max_dimensions": 1536, "supports_dimensions": true, "max_tokens": 8191, "pricing": { "per_million_tokens": 0.02, "currency": "USD" } }, { "id": "text-embedding-3-large", "object": "model", "created": 1754480583, "owned_by": "openai", "name": "Text Embedding 3 Large", "description": "Highest performance OpenAI embedding model with dimension reduction support", "dimensions": 3072, "max_dimensions": 3072, "supports_dimensions": true, "max_tokens": 8191, "pricing": { "per_million_tokens": 0.13, "currency": "USD" } }, { "id": "BAAI/bge-m3", "object": "model", "created": 1754480583, "owned_by": "baai", "name": "BGE M3", "description": "Multilingual embedding model with excellent performance across languages", "dimensions": 1024, "max_dimensions": 1024, "supports_dimensions": false, "max_tokens": 8192, "pricing": { "per_million_tokens": 0.01, "currency": "USD" } } // ... more models ] } ``` ## Field Descriptions | Field | Type | Description | | --------------------- | ------- | ---------------------------------------------------- | | `id` | string | Unique model identifier to use in embedding requests | | `object` | string | Always "model" for OpenAI compatibility | | `created` | number | Unix timestamp of response creation | | `owned_by` | string | Model provider (openai, baai, jina, etc.) | | `name` | string | Human-readable model name | | `description` | string | Detailed model description and use cases | | `dimensions` | number | Default embedding vector dimensions | | `max_dimensions` | number | Maximum embedding vector dimensions supported | | `supports_dimensions` | boolean | Whether custom dimensions are supported | | `max_tokens` | number | Maximum input tokens supported | | `pricing` | object | Pricing information object | ### Pricing Object Structure | Field | Type | Description | | -------------------- | ------ | ------------------------------ | | `per_million_tokens` | number | Cost per million tokens in USD | | `currency` | string | Always "USD" | ## Model Categories ### OpenAI Models High-quality embeddings with dimension reduction support: * `text-embedding-3-small` - Balance of cost and performance * `text-embedding-3-large` - Maximum accuracy * `text-embedding-ada-002` - Legacy model ### Multilingual Models Support for multiple languages: * `BAAI/bge-m3` - Excellent multilingual support * `jina-clip-v1` - Multimodal CLIP embeddings ### Language-Specific Models Optimized for specific languages: * English: `BAAI/bge-base-en-v1.5`, `BAAI/bge-large-en-v1.5`, `jina-embeddings-v2-base-en` * Chinese: `BAAI/bge-large-zh-v1.5`, `jina-embeddings-v2-base-zh`, `zhipu-embedding-2` * German: `jina-embeddings-v2-base-de` * Spanish: `jina-embeddings-v2-base-es` ### Specialized Models Domain-specific embeddings: * `jina-embeddings-v2-base-code` - Optimized for code * `Baichuan-Text-Embedding` - General purpose * `Qwen/Qwen3-Embedding-0.6B` - Efficient with dimension reduction * `Qwen/Qwen3-Embedding-4B` - Higher quality Qwen embeddings * `Qwen/Qwen3-Embedding-8B` - Long-context Qwen embeddings * `BAAI/bge-reranker-large` - Reranking model * `jina-embeddings-v3` / `jina-embeddings-v4` - Newer Jina embedding models * `gemini-embedding-001` - Gemini embeddings * `doubao-embedding-large-text-240915` - High-dimensional embeddings (4096 dims) ## Usage Examples ### Basic Request ```bash theme={null} curl "https://nano-gpt.com/api/v1/embedding-models" ``` ### With Authentication ```bash theme={null} curl "https://nano-gpt.com/api/v1/embedding-models" \ -H "Authorization: Bearer your_api_key_here" ``` ### Python Example ```python theme={null} import requests # Discover available embedding models response = requests.get("https://nano-gpt.com/api/v1/embedding-models") models = response.json() # Display models sorted by price for model in sorted(models["data"], key=lambda x: x["pricing"]["per_million_tokens"]): print(f"{model['id']}: ${model['pricing']['per_million_tokens']}/1M tokens - {model['dimensions']} dims") ``` ### JavaScript Example ```javascript theme={null} // Discover available embedding models const response = await fetch("https://nano-gpt.com/api/v1/embedding-models"); const models = await response.json(); // Find models that support dimension reduction const flexibleModels = models.data.filter(m => m.supports_dimensions); console.log("Models with dimension reduction:", flexibleModels.map(m => m.id)); ``` ## Model Selection Guide | Use Case | Recommended Models | Rationale | | ---------------------- | --------------------------------------- | ------------------------------------- | | General English text | `text-embedding-3-small` | Best price/performance ratio | | Maximum accuracy | `text-embedding-3-large` | Highest quality embeddings | | Multilingual content | `BAAI/bge-m3` | Excellent cross-language performance | | Code embeddings | `jina-embeddings-v2-base-code` | Specialized for programming languages | | Budget-conscious | `BAAI/bge-large-en-v1.5` | \$0.01/1M tokens | | Chinese content | `BAAI/bge-large-zh-v1.5` | Optimized for Chinese | | Fast similarity search | Models with `supports_dimensions: true` | Can reduce dimensions for speed | # Embeddings Source: https://docs.nano-gpt.com/api-reference/endpoint/embeddings POST /v1/embeddings Create embeddings for text using OpenAI-compatible and alternative embedding models ## Overview Create embeddings for text using OpenAI-compatible and alternative embedding models. NanoGPT supports 20+ embedding models (and this list changes over time); use `GET /api/v1/embedding-models` for the source-of-truth list. ## Available Models ### OpenAI Models * `text-embedding-3-small` - 1536 dimensions, \$0.02/1M tokens - Most cost-effective with dimension reduction support * `text-embedding-3-large` - 3072 dimensions, \$0.13/1M tokens - Highest performance with dimension reduction support * `text-embedding-ada-002` - 1536 dimensions, \$0.10/1M tokens - Legacy model ### Alternative Models **Multilingual:** * `BAAI/bge-m3` - 1024 dimensions, \$0.01/1M tokens - Multilingual support * `jina-clip-v1` - 768 dimensions, \$0.04/1M tokens - Multimodal CLIP embeddings **Language-Specific:** * `BAAI/bge-base-en-v1.5` - 768 dimensions, \$0.01/1M tokens - English (base) * `BAAI/bge-large-en-v1.5` - 1024 dimensions, \$0.01/1M tokens - English optimized * `BAAI/bge-large-zh-v1.5` - 1024 dimensions, \$0.01/1M tokens - Chinese optimized * `jina-embeddings-v2-base-en` - 768 dimensions, \$0.05/1M tokens - English * `jina-embeddings-v2-base-de` - 768 dimensions, \$0.05/1M tokens - German * `jina-embeddings-v2-base-zh` - 768 dimensions, \$0.05/1M tokens - Chinese * `jina-embeddings-v2-base-es` - 768 dimensions, \$0.05/1M tokens - Spanish **Specialized:** * `BAAI/bge-reranker-large` - 1024 dimensions, \$0.01/1M tokens - Reranker * `jina-embeddings-v2-base-code` - 768 dimensions, \$0.05/1M tokens - Code embeddings * `Baichuan-Text-Embedding` - 1024 dimensions, \$0.088/1M tokens * `netease-youdao/bce-embedding-base_v1` - 1024 dimensions, \$0.02/1M tokens * `zhipu-embedding-2` - 1024 dimensions, \$0.07/1M tokens * `Qwen/Qwen3-Embedding-0.6B` - 1024 dimensions, \$0.01/1M tokens - Supports dimension reduction * `Qwen/Qwen3-Embedding-4B` - 1536 dimensions, \$0.03/1M tokens - Supports dimension reduction * `Qwen/Qwen3-Embedding-8B` - 1536 dimensions, \$0.05/1M tokens - Supports dimension reduction * `jina-embeddings-v3` - 1024 dimensions, \$0.10/1M tokens * `jina-embeddings-v4` - 2048 dimensions, \$0.10/1M tokens * `gemini-embedding-001` - 3072 dimensions, \$0.15/1M tokens * `doubao-embedding-large-text-240915` - 4096 dimensions, \$0.10/1M tokens ## Request Parameters | Parameter | Type | Required | Description | | ----------------- | --------------- | -------- | ---------------------------------------------------------- | | `input` | string or array | Yes | Single text string or array of up to 2048 strings to embed | | `model` | string | Yes | ID of the embedding model to use | | `encoding_format` | string | No | Format for embeddings: `"float"` (default) or `"base64"` | | `dimensions` | integer | No | Reduce embedding dimensions (only for supported models) | | `user` | string | No | Optional identifier for tracking usage | ## Response Format ```json theme={null} { "object": "list", "data": [ { "object": "embedding", "index": 0, "embedding": [0.023, -0.012, 0.045, ...] } ], "model": "text-embedding-3-small", "usage": { "prompt_tokens": 8, "total_tokens": 8 } } ``` ## Code Examples ### Python with OpenAI SDK ```python theme={null} from openai import OpenAI # Initialize client pointing to NanoGPT client = OpenAI( api_key="YOUR_NANOGPT_API_KEY", base_url="https://nano-gpt.com/api/v1" ) # Create embedding response = client.embeddings.create( input="Your text to embed", model="text-embedding-3-small" ) # Access the embedding embedding = response.data[0].embedding print(f"Embedding dimensions: {len(embedding)}") ``` ### JavaScript/TypeScript ```javascript theme={null} import OpenAI from 'openai'; // Initialize client pointing to NanoGPT const openai = new OpenAI({ apiKey: 'YOUR_NANOGPT_API_KEY', baseURL: 'https://nano-gpt.com/api/v1' }); // Create embedding const response = await openai.embeddings.create({ input: "Your text to embed", model: "text-embedding-3-small" }); // Access the embedding const embedding = response.data[0].embedding; console.log(`Embedding dimensions: ${embedding.length}`); ``` ### cURL ```bash theme={null} curl https://nano-gpt.com/api/v1/embeddings \ -H "Authorization: Bearer YOUR_NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "input": "Your text to embed", "model": "text-embedding-3-small" }' ``` ### Batch Processing ```python theme={null} # Process multiple texts in a single request texts = [ "First text to embed", "Second text to embed", "Third text to embed" ] response = client.embeddings.create( input=texts, # Pass array of strings model="text-embedding-3-small" ) # Access embeddings by index for i, data in enumerate(response.data): print(f"Text {i}: {len(data.embedding)} dimensions") ``` ### Dimension Reduction For models that support it (`text-embedding-3-small`, `text-embedding-3-large`, `Qwen/Qwen3-Embedding-0.6B`, `Qwen/Qwen3-Embedding-4B`, `Qwen/Qwen3-Embedding-8B`): ```python theme={null} # Reduce dimensions to 256 for faster similarity comparisons response = client.embeddings.create( input="Your text to embed", model="text-embedding-3-small", dimensions=256 # Reduce from 1536 to 256 ) ``` ## Use Cases ### Semantic Search ```python theme={null} import numpy as np from sklearn.metrics.pairwise import cosine_similarity # Create embeddings for your documents documents = ["Document 1 text", "Document 2 text", "Document 3 text"] doc_embeddings = [] for doc in documents: response = client.embeddings.create(input=doc, model="text-embedding-3-small") doc_embeddings.append(response.data[0].embedding) # Create embedding for search query query = "Search query text" query_response = client.embeddings.create(input=query, model="text-embedding-3-small") query_embedding = query_response.data[0].embedding # Calculate similarities similarities = cosine_similarity([query_embedding], doc_embeddings)[0] # Find most similar documents top_matches = np.argsort(similarities)[::-1][:3] for idx in top_matches: print(f"Document {idx}: {similarities[idx]:.3f} similarity") ``` ### RAG (Retrieval Augmented Generation) ```python theme={null} # 1. Embed and store your knowledge base knowledge_base = [ {"text": "Fact 1...", "embedding": None}, {"text": "Fact 2...", "embedding": None}, ] for item in knowledge_base: response = client.embeddings.create( input=item["text"], model="text-embedding-3-small" ) item["embedding"] = response.data[0].embedding # 2. For a user query, find relevant context user_query = "What is...?" query_response = client.embeddings.create( input=user_query, model="text-embedding-3-small" ) query_embedding = query_response.data[0].embedding # 3. Find most relevant facts # relevant_facts = find_similar_texts(query_embedding, knowledge_base, top_k=3) # 4. Use retrieved context with chat completion chat_response = client.chat.completions.create( model="gpt-4", messages=[ {"role": "system", "content": f"Context: {relevant_facts}"}, {"role": "user", "content": user_query} ] ) ``` ## Best Practices ### Model Selection * **General English text**: Use `text-embedding-3-small` for best price/performance * **Maximum accuracy**: Use `text-embedding-3-large` * **Multilingual**: Use `BAAI/bge-m3` or language-specific Jina models * **Code**: Use `jina-embeddings-v2-base-code` * **Budget-conscious**: Use BAAI models at \$0.01/1M tokens ### Performance Optimization * **Batch requests**: Send up to 2048 texts in a single request * **Use dimension reduction**: Reduce dimensions for faster similarity calculations when exact precision isn't critical * **Cache embeddings**: Store computed embeddings to avoid re-processing identical texts * **Choose appropriate models**: Don't use 3072-dimension models if 768 dimensions suffice ### Cost Optimization * **Monitor token usage**: Track the `usage` field in responses * **Use smaller models**: Start with `text-embedding-3-small` before upgrading * **Implement caching**: Avoid re-embedding identical content * **Batch processing**: Reduce API call overhead ## Rate Limits Rate limits vary by endpoint and account. See [Rate Limits](/api-reference/miscellaneous/rate-limits). ## Error Handling The API returns standard HTTP status codes and OpenAI-compatible error responses: See also: [Error Handling](/api-reference/miscellaneous/error-handling). ```json theme={null} { "error": { "message": "Invalid model specified", "type": "invalid_request_error", "param": "model", "code": null } } ``` Common error codes: * `401`: Invalid or missing API key * `400`: Invalid request parameters * `429`: Rate limit exceeded * `500`: Server error # Generate Images Source: https://docs.nano-gpt.com/api-reference/endpoint/image-api-generate POST https://nano-gpt.com/api/v1/images Generate text-to-image and image-to-image outputs through NanoGPT's normalized Image API endpoint ## Overview Use `POST /api/v1/images` to generate images through NanoGPT's normalized Image API. The endpoint accepts JSON only and supports text-to-image and image-to-image requests for models that expose those capabilities. Discover model-specific parameters first with: * `GET /api/v1/images/models` * `GET /api/v1/images/models/{modelId}/endpoints` ## Authentication Use either header: * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Request ```http theme={null} POST /api/v1/images Content-Type: application/json ``` ## Text-To-Image Example ```bash theme={null} curl https://nano-gpt.com/api/v1/images \ -H "Content-Type: application/json" \ -H "x-api-key: $NANOGPT_API_KEY" \ -d '{ "model": "gpt-image-2", "prompt": "A clean product photo of a matte black espresso machine on a white counter", "resolution": "1024x1024", "quality": "medium", "n": 1 }' ``` ## Image-To-Image Example ```json theme={null} { "model": "gpt-image-2", "prompt": "Make this look like a polished studio product photo", "input_references": [ { "type": "image_url", "image_url": { "url": "https://example.com/reference.png" } } ], "resolution": "1024x1024", "quality": "medium", "n": 1 } ``` ## Request Fields | Field | Type | Required | Description | | ------------------ | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `model` | string | Yes | Image model ID from `GET /api/v1/images/models`. | | `prompt` | string | Usually | Text prompt or edit instruction. Model requirements can vary. | | `n` | integer | No | Number of output images. Internally normalized to `nImages`; if both are supplied, `nImages` takes precedence. | | `resolution` | string | No | Output resolution when supported by the model. | | `aspect_ratio` | string | No | Output aspect ratio when supported by the model. | | `quality` | string | No | Quality tier when supported by the model. | | `output_format` | string | No | Output format when supported by the model. | | `seed` | integer | No | Optional model-specific seed that may improve reproducibility where supported by the model/provider route. Identical results are not guaranteed. Check the selected model's `supported_parameters` metadata before using this field. | | `input_references` | array | No | Image references for image-to-image models. | NanoGPT exposes many model-specific parameters. Treat parameter support as discoverable through `supported_parameters`, not globally available. ## Input References `input_references` accepts an array of image references. Supported entries: ```json theme={null} [ "https://example.com/image.png", "data:image/png;base64,...", { "type": "image_url", "image_url": { "url": "https://example.com/image.png" } } ] ``` Do not mix `input_references` with legacy image aliases such as `imageDataUrl`, `imageDataUrls`, `image_url`, or `images` in the same request. The route returns `conflicting_image_inputs` if both styles are supplied. ## Unsupported Features `stream: true` is not supported yet. Provider selection and provider passthrough are not supported yet. Non-empty `provider` objects return `unsupported_provider_options`. ## Common Errors | Code | Description | | ------------------------------ | -------------------------------------------------------------------------------------------- | | `missing_model` | `model` is required. | | `invalid_content_type` | `POST /api/v1/images` accepts `application/json` requests only. | | `invalid_input_references` | `input_references` must be an array of image URL strings, data URLs, or `image_url` objects. | | `conflicting_image_inputs` | Use `input_references` or legacy image input aliases, not both. | | `unsupported_stream` | `stream: true` is not supported yet. | | `unsupported_provider_options` | Provider selection and passthrough options are not supported yet. | ## Notes * Use `n` for output count. If your request also sends `nImages`, `nImages` takes precedence. * Use [Image API](/api-reference/image-generation) for the full guide and error response examples. # Get Image Model Endpoints Source: https://docs.nano-gpt.com/api-reference/endpoint/image-api-model-endpoints GET https://nano-gpt.com/api/v1/images/models/{modelId}/endpoints Inspect endpoint metadata, pricing, and image-input constraints for an image model ## Overview Use `GET /api/v1/images/models/{modelId}/endpoints` to inspect public endpoint metadata for a specific image model. The response includes supported parameters, public NanoGPT pricing, streaming support, and image reference constraints when the model supports image inputs. For model IDs that contain slashes, use the `endpoints` URL returned by `GET /api/v1/images/models`. ## Authentication Authentication is optional. * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Request ```http theme={null} GET /api/v1/images/models/gpt-image-2/endpoints ``` ## Response ```json theme={null} { "id": "gpt-image-2", "endpoints": [ { "provider_name": "OpenAI", "provider_slug": "openai", "provider_tag": null, "supported_parameters": { "resolution": { "type": "enum", "values": ["1024x1024", "1024x768"], "default": "1024x1024" }, "n": { "type": "range", "min": 1, "max": 4, "default": 1 } }, "allowed_passthrough_parameters": [], "supports_streaming": false, "pricing": [ { "billable": "output_image", "unit": "image", "cost_usd": 0.06551, "resolution": "1024x1024" } ], "input_reference_constraints": { "max_items": 4, "route": { "min_width": 8, "min_height": 8, "max_bytes": 31457280, "formats": ["png", "jpeg", "webp"], "source": "route-preflight" } } } ] } ``` ## Fields | Field | Type | Description | | -------------------------------- | -------------- | -------------------------------------------------------------------------------------------- | | `id` | string | Image model ID. | | `endpoints` | array | Public endpoint metadata for the model. Currently one public endpoint is returned per model. | | `provider_name` | string | Public provider display name. | | `provider_slug` | string | Public provider slug. | | `provider_tag` | string or null | Currently `null`; provider selection is not exposed on this API surface. | | `supported_parameters` | object | Machine-readable parameter metadata for this model endpoint. | | `allowed_passthrough_parameters` | array | Empty for now; provider passthrough is not supported. | | `supports_streaming` | boolean | Currently `false`. | | `pricing` | array | Public NanoGPT pricing metadata. This is not provider at-cost pricing. | | `input_reference_constraints` | object | Included when the model supports image inputs. | ## Example ```bash theme={null} curl https://nano-gpt.com/api/v1/images/models/gpt-image-2/endpoints ``` ## Notes * Provider selection, provider passthrough options, and provider-specific endpoint choice are not supported yet. * `pricing` uses public NanoGPT pricing. * Check `supported_parameters` before deciding which request controls to show. # List Image Models Source: https://docs.nano-gpt.com/api-reference/endpoint/image-api-models GET https://nano-gpt.com/api/v1/images/models List image models for NanoGPT's dedicated Image API ## Overview Use `GET /api/v1/images/models` to discover image models for the dedicated Image API. The response includes model IDs, capabilities, supported parameters, streaming support, and a model-specific endpoint metadata URL. Model availability and supported parameters can change. Build clients from the returned metadata instead of hardcoding model capability tables. ## Authentication Authentication is optional. * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Response ```json theme={null} { "object": "list", "data": [ { "id": "gpt-image-2", "name": "GPT Image 2", "description": "Model description...", "created": 1760000000, "owned_by": "openai", "architecture": { "input_modalities": ["text", "image"], "output_modalities": ["image"] }, "supported_parameters": { "resolution": { "type": "enum", "values": ["1024x1024", "1024x768"], "default": "1024x1024" }, "n": { "type": "range", "min": 1, "max": 4, "default": 1 } }, "supports_streaming": false, "endpoints": "/api/v1/images/models/gpt-image-2/endpoints", "capabilities": { "image_generation": true, "image_to_image": true, "inpainting": false, "nsfw": false }, "category": "image" } ], "meta": { "count": 201, "generated_at": "2026-06-25T00:00:00.000Z" } } ``` ## Fields | Field | Type | Description | | -------------------------------- | --------- | ------------------------------------------------------------------------------------- | | `id` | string | Model ID to use in `POST /api/v1/images`. | | `name` | string | Human-readable model name. | | `description` | string | Model description. | | `created` | integer | Unix timestamp for when the model was added. | | `owned_by` | string | Public owner or provider label. | | `architecture.input_modalities` | string\[] | Supported input modalities, such as `text` or `image`. | | `architecture.output_modalities` | string\[] | Supported output modalities. | | `supported_parameters` | object | Machine-readable model-specific parameter metadata. | | `supports_streaming` | boolean | Currently always `false`. | | `endpoints` | string | URL for endpoint metadata and pricing for this model. | | `capabilities` | object | Feature flags such as `image_generation`, `image_to_image`, `inpainting`, and `nsfw`. | | `category` | string | Usually `image`. | ## Example ```bash theme={null} curl https://nano-gpt.com/api/v1/images/models ``` ## Notes * `supported_parameters` varies by model. * Model IDs may contain slashes. Prefer the returned `endpoints` URL when fetching endpoint metadata. * Use this endpoint with [Get Image Model Endpoints](/api-reference/endpoint/image-api-model-endpoints) before sending generation requests. # Image Edits Source: https://docs.nano-gpt.com/api-reference/endpoint/image-edits POST https://nano-gpt.com/api/v1/images/edits OpenAI-compatible image editing endpoint with multipart and JSON inputs ## Overview Use the image edits endpoint to modify one or more input images with a text prompt. This is an OpenAI-compatible image editing endpoint. For new JSON-only image-to-image generation flows, also consider the dedicated [Image API](/api-reference/image-generation), which uses `input_references` and `POST /api/v1/images`. Both paths are supported aliases: * `POST /api/v1/images/edit` * `POST /api/v1/images/edits` ## Authentication Use either header: * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Accountless x402 Payment JSON image edit requests can be quoted without an account or API key on supported deployments when you explicitly opt in to the quote flow: ```bash theme={null} curl -i https://nano-gpt.com/api/v1/images/edits \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "model": "gpt-image-1", "prompt": "Remove the background", "imageDataUrl": "data:image/png;base64,..." }' ``` To request an accountless x402 quote, send the API request without `Authorization` or `x-api-key`, and include `x-x402: true`. NanoGPT will return `402 Payment Required` with available payment options. This endpoint supports accountless x402 payments where listed by `GET /api/v1/x402/endpoints`, including Lightning L402 when advertised. Multipart uploads require normal authentication before conversion. See [Accountless x402 API Payments](/api-reference/miscellaneous/x402) for the full flow. If you receive `401 missing_api_key` immediately, check that the initial quote request includes `x-x402: true`. Without that header, NanoGPT does not enter the x402 quote flow. ## Multipart Request Send `multipart/form-data` when uploading files. | Field | Type | Required | Description | | --------- | ----------------------- | -------- | --------------------------------------------------------------------------------- | | `prompt` | string | Yes | Edit instruction. | | `image` | file or string | Yes\* | Input image file, image URL, or data URL. | | `image[]` | repeated file or string | Yes\* | Repeated image inputs for multi-image editing. | | `mask` | file | No | Optional mask image for inpainting-capable models. | | `model` | string | No | Image-edit-capable model. Defaults to an image-edit-capable default when omitted. | | `size` | string | No | Requested output size, when supported by the model. | | `n` | integer | No | Number of outputs, when supported by the model. | \*Provide `image` or one or more `image[]` fields. Other image-generation parameters may pass through when supported by the selected model. ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/images/edits \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -F model=gpt-image-1 \ -F prompt="Remove the background" \ -F image=@product.png \ -F size=1024x1024 ``` ### Multi-image Multipart ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/images/edit \ -H "x-api-key: $NANOGPT_API_KEY" \ -F model=gpt-image-1 \ -F prompt="Combine these references into one product image" \ -F "image[]=@reference-1.png" \ -F "image[]=@reference-2.jpg" ``` ## JSON Request Send JSON when you already have images as data URLs. ```json theme={null} { "model": "gpt-image-1", "prompt": "Remove the background", "imageDataUrl": "data:image/png;base64,...", "maskDataUrl": "data:image/png;base64,..." } ``` ### Multi-image JSON ```json theme={null} { "model": "gpt-image-1", "prompt": "Combine these references into one product image", "imageDataUrls": [ "data:image/png;base64,...", "data:image/jpeg;base64,..." ] } ``` ## Response Responses follow the same image response conventions as image generation. Depending on request parameters and model support, returned items may contain `url` or `b64_json`. ```json theme={null} { "created": 1778155200, "data": [ { "url": "https://..." } ] } ``` ## Common Errors | Code | Description | | ------------------------ | ----------------------------------- | | `missing_image_input` | `image` is required. | | `image_input_too_large` | Upload or input image is too large. | | `invalid_multipart_body` | Multipart body could not be parsed. | | `rate_limit_exceeded` | Too many image edit uploads. | ## Notes * Multipart uploads have strict size limits. * Generated URLs are temporary unless another page explicitly guarantees longer retention. * Use `GET /api/v1/image-models` to discover image-edit-capable models and supported parameters. # Image Generation (OpenAI-Compatible) Source: https://docs.nano-gpt.com/api-reference/endpoint/image-generation-openai POST /v1/images/generations Creates an image generation for the provided prompt (OpenAI-compatible). For unauthenticated accountless x402 quote requests, include x-x402: true. ## Overview Generate images from text prompts or base64 image inputs using the OpenAI-compatible endpoint. Responses include base64 bytes (`b64_json`) by default or signed URLs (`url`) when `response_format: "url"`. For new integrations that do not need OpenAI-compatible request shapes, use the dedicated [Image API](/api-reference/image-generation). It supports image model discovery, endpoint metadata, public pricing metadata, and normalized generation through `POST /api/v1/images`. ## Endpoint * Method/Path: `POST https://nano-gpt.com/v1/images/generations` * Auth: `Authorization: Bearer ` * Required header: `Content-Type: application/json` ## Request Body (JSON) Core fields: * `prompt` (string, required): Text prompt to generate an image from. * `model` (string, optional): Model ID (default `hidream`). * `n` (integer, optional): Number of images to generate (default `1`). * `size` (string, optional): Requested output size or model-specific resolution value. Use `GET /api/v1/image-models?detailed=true` and read `supported_parameters.resolutions` for the selected model's supported values. * `response_format` (string, optional): `b64_json` (default) or `url`. * `user` (string, optional): End-user identifier. Image inputs (img2img/inpainting): * `imageDataUrl` (string, optional): Base64 data URL for a single input image. * `imageDataUrls` (array, optional): Multiple base64 data URLs for supported models. * `maskDataUrl` (string, optional): Base64 mask data URL for inpainting. Generation controls (model-specific): * `strength`, `guidance_scale`, `num_inference_steps`, `kontext_max_mode`. * `seed` is an optional model-specific hint that may improve reproducibility where supported. Identical results are not guaranteed. Check the selected model's `supported_parameters` metadata before using this field. See [Supported Parameter Discovery](/api-reference/image-generation#supported-parameter-discovery) for the discovery workflow. If a model/provider route offers a stronger reproducibility guarantee, rely only on documentation for that specific route. For OpenAI-compatible image editing, use [Image Edits](/api-reference/endpoint/image-edits). ## Response * Each `data[i]` contains either `b64_json` (default) or `url` (when `response_format: "url"`), never both. * When requesting `response_format: "url"`, the API may still return `b64_json` if URL generation (upload/presign) fails, as a fallback. * Signed URLs expire after a short period (currently \~1 hour). Download promptly for long-term storage. ## Examples ```bash cURL (basic) theme={null} curl https://nano-gpt.com/v1/images/generations \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "hidream", "prompt": "A sunset over a mountain range", "n": 1, "size": "1024x1024" }' ``` ```bash cURL (response_format: "url") theme={null} curl https://nano-gpt.com/v1/images/generations \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "hidream", "prompt": "A neon city skyline at night", "n": 1, "size": "1024x1024", "response_format": "url" }' ``` ```python Python (img2img) theme={null} import base64 import requests API_KEY = "YOUR_API_KEY" with open("input.jpg", "rb") as f: encoded = base64.b64encode(f.read()).decode("utf-8") image_data_url = f"data:image/jpeg;base64,{encoded}" response = requests.post( "https://nano-gpt.com/v1/images/generations", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", }, json={ "model": "flux-kontext", "prompt": "Transform this image into a watercolor painting", "n": 1, "size": "1024x1024", "imageDataUrl": image_data_url, }, ) result = response.json() ``` ## Notes & Limits * Input images must be provided as base64 data URLs; download and convert remote images before sending. * Uploads should be 4 MB or smaller after encoding. Compress or resize large assets before sending. * Use `GET /api/v1/image-models?detailed=true` to discover current image model capabilities and supported resolution values. For seed and other model-specific fields, also consult the [Image API supported-parameter guidance](/api-reference/image-generation#supported-parameter-discovery). # Image Models Source: https://docs.nano-gpt.com/api-reference/endpoint/image-models GET https://nano-gpt.com/api/v1/image-models List available image generation and image editing models with capabilities and supported parameters ## Overview Use `GET /api/v1/image-models` to discover the currently available image models and their capabilities. Do not hardcode image model capability tables in your client; model availability and supported parameters can change. For new integrations using the normalized Image API, prefer [`GET /api/v1/images/models`](/api-reference/endpoint/image-api-models). That newer route returns model-specific `supported_parameters`, endpoint metadata links, and input-reference capabilities for use with [`POST /api/v1/images`](/api-reference/endpoint/image-api-generate). This endpoint is cacheable. Refresh it periodically and handle new fields as additive. ## Endpoint ```text theme={null} GET https://nano-gpt.com/api/v1/image-models ``` ## Authentication Authentication is optional. * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Query Parameters | Parameter | Type | Default | Description | | ---------- | ------- | ------- | ----------------------------------------------------------------------------- | | `detailed` | boolean | `true` | Include names, descriptions, pricing, capabilities, and supported parameters. | ## Response ```json theme={null} { "object": "list", "data": [ { "id": "pruna-ai/p-image/text-to-image", "object": "model", "created": 1778544000, "owned_by": "prunaai", "name": "P-Image", "description": "Fast text-to-image generation for low-cost drafts, social visuals, and rapid creative iteration.", "architecture": { "modality": "text->image", "input_modalities": ["text"], "output_modalities": ["image"] }, "pricing": { "per_image": { "1024*1024": 0.005, "1376*768": 0.005 }, "currency": "USD" }, "capabilities": { "image_generation": true, "image_to_image": false, "inpainting": false, "nsfw": false }, "supported_parameters": { "resolutions": ["1024x1024", "1376x768", "1184x896"], "max_images": 4 }, "icon_url": "/icons/PrunaAI.svg", "tags": ["text-to-image", "prunaai", "wavespeed"], "category": "image" } ], "meta": { "count": 169, "generated_at": "2026-05-14T00:00:00.000Z" } } ``` ## Supported Parameters Supported parameters vary by model. Common fields include: * `resolutions`: array of supported resolution values for this model. * `max_images`: maximum number of images that can be requested at once. * `rendering_speed`: available rendering speed values, when the model exposes speed tiers. * `fixed_image_count`: fixed output count for models that do not allow custom `n`. Use `supported_parameters.resolutions` to populate resolution or size controls. Treat the values as model-specific strings. Most image generation models use pixel dimensions such as `1024x1024`, but some models use values such as `auto`, aspect ratios, quality tiers, or scale factors. Use the model's `supported_parameters` and `capabilities` objects to decide which controls to show and which request fields to send. ## Fields | Field | Type | Description | | ---------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Model ID to pass to image generation or image editing endpoints. | | `object` | string | Always `model`. | | `created` | integer | Unix timestamp derived from the model's added date. | | `owned_by` | string | Provider or owner identifier. | | `name` | string | Human-readable model name. | | `description` | string | Short model description. | | `architecture` | object | Modality metadata, including input and output modalities. | | `pricing` | object | USD pricing metadata. Shape can vary by model. | | `capabilities` | object | Feature flags such as `image_generation`, `image_to_image`, and `inpainting`. | | `capabilities.nsfw` | boolean | Whether the model is uncensored / NSFW-capable. | | `supported_parameters` | object | Model-specific parameters such as sizes, counts, quality settings, seeds, or image input controls. | | `supported_parameters.resolutions` | string\[] | Model-specific supported output resolution values. Usually pixel dimensions like `1024x1024`; may also be symbolic values such as `auto` or model-specific scale factors. | | `supported_parameters.max_images` | integer | Maximum number of images the model supports per request. | | `supported_parameters.rendering_speed` | string\[] | Optional list of rendering speed tiers. Present only for models that expose speed choices. | | `supported_parameters.fixed_image_count` | integer | Optional fixed output image count. Present when the model always returns a fixed number of images. | | `icon_url` | string | Optional provider icon path. | | `tags` | string\[] | Optional model tags. | | `category` | string | Model category. Usually `image`. | `supported_parameters`, including `supported_parameters.resolutions`, is included only in detailed responses. `detailed=true` is the default. If you pass `detailed=false`, each item may include only basic identifiers such as `id`, `object`, `created`, and `owned_by`. ## Example ```bash theme={null} curl "https://nano-gpt.com/api/v1/image-models?detailed=true" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ## Notes * Supported parameters vary by model. * Use this endpoint instead of hardcoding media model capabilities. * The response is cacheable, but model availability can change. * Pricing values are in USD unless otherwise documented in the response. # Create Invitation Source: https://docs.nano-gpt.com/api-reference/endpoint/invitations-create POST /invitations/create Create an invitation or referral link with an optional credit amount. ## Overview Create an invitation or referral link with an optional credit amount. Invitation links can include a fixed credit amount, while referral links always have a zero amount. ## Authentication This endpoint supports two authentication methods: * Session-based authentication (browser session cookie) * API key authentication via request headers ### API Key Headers * `x-api-key: YOUR_API_KEY` or * `Authorization: Bearer YOUR_API_KEY` ## Request ### Headers * `Content-Type: application/json` * `x-api-key: YOUR_API_KEY` (required for API key auth) ### Body | Field | Type | Required | Description | | --------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------- | | `type` | string | No | Either `"invitation"` or `"referralLink"`. Defaults to `"invitation"`. | | `amount` | number | No | Credit amount to include with the invitation. Must be non-negative. Ignored for referral links (always 0). | | `currency` | string | No | Currency for the amount. Required if `amount > 0`. Use `"USD"` for US dollars. | | `recipientName` | string | No | Name of the invitation recipient. | | `issuerName` | string | No | Name of the person sending the invitation. | | `issuerNote` | string | No | Personal note to include with the invitation. | ## Response ### Success (200 OK) ```json theme={null} { "insertId": "12345", "redeemCode": "ABC123XY", "url": "https://example.com/invite/ABC123XY", "type": "invitation", "amount": 10, "currency": "USD" } ``` | Field | Type | Description | | ------------ | -------------- | ------------------------------------------------------------------------------ | | `insertId` | string | Database ID of the created invitation. | | `redeemCode` | string | Unique code the recipient uses to redeem the invitation. | | `url` | string | Full URL to share with the recipient. | | `type` | string | The invitation type (`"invitation"` or `"referralLink"`). | | `amount` | number | Credit amount attached to this invitation. | | `currency` | string \| null | Currency of the amount, or `null` if amount is 0 (recipient chooses currency). | ## Errors | Status | Body | Description | | ------ | ------------------------------- | -------------------------------------------------------------- | | 400 | `"Invalid request body"` | Request body is not valid JSON. | | 400 | `"InvalidAmount"` | The provided amount is negative. | | 400 | `"InsufficientBalance"` | Your account balance is insufficient for the specified amount. | | 401 | `"Invalid session"` | Session is missing required data. | | 401 | `"Unable to get session"` | Authentication failed. | | 500 | `"Failed to create invitation"` | Server error during invitation creation. | ## Notes * When `amount` is 0 or not provided, the `currency` in the response is `null`, allowing the recipient to choose their preferred currency upon redemption. * Referral links always have an amount of 0, regardless of what value is passed. * The `redeemCode` uses a URL-safe alphabet that avoids ambiguous characters (no 0, O, 1, l). * Ensure your account has sufficient balance before creating invitations with credit amounts. ## Examples ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/invitations/create \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "amount": 10, "currency": "USD", "recipientName": "Jane Doe", "issuerName": "John Smith", "issuerNote": "Welcome to the platform!" }' ``` ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/invitations/create \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "type": "referralLink", "issuerName": "John Smith" }' ``` ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/invitations/create \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "recipientName": "Jane Doe" }' ``` # Context Memory (Standalone) Source: https://docs.nano-gpt.com/api-reference/endpoint/memory POST /v1/memory Compress a conversation with Context Memory and return compressed messages and usage (no model inference) ## Overview The standalone Context Memory endpoint compresses an entire conversation into a single memory message. This endpoint does not run a model. It returns the compressed memory message and usage so you can pipe it into your own chat completion request or store it. * No model inference is performed * Pass your `messages` array and optional settings ## Authentication * `Authorization: Bearer YOUR_API_KEY` or * `x-api-key: YOUR_API_KEY` ## Request ### Headers * `Content-Type: application/json` * `Authorization: Bearer YOUR_API_KEY` or `x-api-key: YOUR_API_KEY` * `memory_expiration_days: <1..365>` (optional) — overrides body; defaults to 30 ### Body ```json theme={null} { "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Summarize our previous discussion and continue." } ], "expiration_days": 45, "model_context_limit": 128000 } ``` * `messages` (required): OpenAI-style messages. `user`, `assistant`, `system`, `tool`, and `function` roles are accepted. Assistant `tool_calls` are ignored during compression. * `expiration_days` (optional): 1..365; default 30. If both header and body are provided, the header takes precedence. * `model_context_limit` (optional): Context target for compression. Default 128k; values below 10k are clamped internally. ## Response ### Success (200) ```json theme={null} { "messages": [ { "role": "system", "content": "..." } ], "usage": { "prompt_tokens": 51234, "completion_tokens": 1234, "total_tokens": 52468, "prompt_tokens_details": { "cached_tokens": 4096 } } } ``` * `messages`: The single memory-compressed message array to use as your full context in a chat completion request * `usage`: Token usage. When available, `prompt_tokens_details.cached_tokens` indicates discounted cached input tokens ### Error Examples ```json 400 Bad Request theme={null} { "error": "messages must be a non-empty array" } ``` ```json 401 Unauthorized theme={null} { "error": "Invalid session" } ``` ```json 402 Payment Required theme={null} { "error": "Insufficient balance" } ``` ```json 429 Too Many Requests theme={null} { "error": "Rate limit exceeded. Please wait before sending another request." } ``` ## Pricing & Billing * Non-cached input tokens: \$5.00 / 1M * Cached input tokens: \$2.50 / 1M (when applicable) * Output tokens: \$10.00 / 1M Note: This endpoint only charges for memory compression. If you later call `/v1/chat/completions`, model costs are billed separately. ## Retention * Default retention: 30 days * Configure via body `expiration_days` or header `memory_expiration_days` * Header value takes precedence over body when both are supplied ## Examples ```javascript JavaScript theme={null} const res = await fetch('https://nano-gpt.com/api/v1/memory', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json', 'memory_expiration_days': '45' }, body: JSON.stringify({ messages: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Optimize our previous plan and continue.' } ] }) }); const { messages, usage } = await res.json(); // Use `messages` as the full context for a subsequent /v1/chat/completions call ``` ```python Python theme={null} import requests url = 'https://nano-gpt.com/api/v1/memory' headers = { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' } data = { 'messages': [ { 'role': 'system', 'content': 'You are a helpful assistant.' }, { 'role': 'user', 'content': 'Summarize and continue.' } ], 'expiration_days': 30, 'model_context_limit': 128000 } r = requests.post(url, headers=headers, json=data) result = r.json() print(result['messages'][0]['content'][:200]) print(result['usage']) ``` ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/v1/memory \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "memory_expiration_days: 90" \ -d '{ "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Compress our conversation and continue."} ] }' ``` # Messages Source: https://docs.nano-gpt.com/api-reference/endpoint/messages POST /v1/messages Accepts Anthropic Messages requests, including video blocks for models that advertise video input. `/v1/messages` accepts **compressed request bodies** (`Content-Encoding: gzip`, `deflate`, or `br`) on authenticated JSON requests — useful for long conversations, where compressed uploads cut time-to-first-token. See [Compressed Request Bodies](/api-reference/miscellaneous/request-compression). ## Overview The `/v1/messages` endpoint provides full Anthropic API compatibility. Clients using the Anthropic SDK can use NanoGPT by simply changing the base URL -- no code changes required. NanoGPT accepts requests in the **Anthropic Messages** format, routes them to the requested NanoGPT model, and returns responses back in the Anthropic Messages shape. For non‑Anthropic models, NanoGPT transparently translates the request to an OpenAI-style chat format internally and then converts the response back to Anthropic Messages format. This endpoint supports: * Text generation (streaming and non-streaming) * Multi-turn conversations * Tool use (function calling) * Vision (images), video understanding, and document/PDF processing * Extended thinking (reasoning models) * Prompt caching * Token estimates via [`POST /api/v1/messages/count_tokens`](/api-reference/endpoint/messages-count-tokens) ## Endpoint ``` POST https://nano-gpt.com/api/v1/messages ``` ## Authentication Use either header: * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Request Format ### Required Fields | Field | Type | Description | | ------------ | ------ | -------------------------------------------------------------------- | | `model` | string | Model identifier (any NanoGPT model, including non‑Anthropic models) | | `max_tokens` | number | Maximum tokens to generate (must be a finite number) | | `messages` | array | Array of conversation messages | ### Optional Fields | Field | Type | Default | Description | | --------------------------- | ---------------- | ------- | --------------------------------------------------------------------------------------- | | `system` | string or array | -- | System prompt (string or array of text blocks) | | `stream` | boolean | `false` | Enable streaming responses | | `temperature` | number | -- | Sampling temperature | | `top_p` | number | -- | Nucleus sampling parameter | | `top_k` | number | -- | Top-k sampling parameter | | `stop_sequences` | string\[] | -- | Custom stop sequences | | `tools` | array | -- | Tool definitions for function calling | | `tool_choice` | string or object | -- | Control tool selection behavior | | `disable_parallel_tool_use` | boolean | -- | Disable parallel tool calls | | `thinking` | object | -- | Enable extended thinking for supported models | | `metadata` | object | -- | Request metadata (`user` or `user_id`) | | `service_tier` | string | -- | Service tier: `"auto"`, `"default"`, `"standard"`, `"flex"`, `"priority"`, or `"batch"` | ### Message Format Messages must have a `role` (`user` or `assistant`) and `content`: ```json theme={null} { "role": "user", "content": "Hello!" } ``` Or with structured content blocks: ```json theme={null} { "role": "user", "content": [ { "type": "text", "text": "What's in this image?" }, { "type": "image", "source": { "type": "base64", "media_type": "image/jpeg", "data": "" } } ] } ``` ### Content Block Types #### Text Block ```json theme={null} { "type": "text", "text": "Your message here" } ``` #### Image Block (for vision-capable models) ```json theme={null} { "type": "image", "source": { "type": "base64", "media_type": "image/jpeg", "data": "" } } ``` Or with URL: ```json theme={null} { "type": "image", "source": { "type": "url", "url": "https://example.com/image.jpg" } } ``` Supported media types: `image/jpeg`, `image/png`, `image/gif`, `image/webp` #### Document Block (for PDF-capable models) ```json theme={null} { "type": "document", "source": { "type": "base64", "media_type": "application/pdf", "data": "" } } ``` #### Video Block (for video-capable models) Use `type: "video"` for new integrations. Inline video bytes use an Anthropic base64 source: ```json theme={null} { "type": "video", "source": { "type": "base64", "media_type": "video/mp4", "data": "AAAA..." } } ``` For URL transport, use an HTTPS URL and declare the video MIME type: ```json theme={null} { "type": "video", "source": { "type": "url", "url": "https://cdn.example.com/clip.mp4", "media_type": "video/mp4" } } ``` A `document` compatibility block with a `video/*` source is normalized as video. A document with `application/pdf` remains a document. Public remote sources must use HTTPS; inline base64 must be valid. See the [Video Input guide](/api-reference/miscellaneous/video-input) for model discovery, limits, errors, segment behavior, and the equivalent Chat Completions and Responses shapes. #### Tool Use Block (in assistant messages) ```json theme={null} { "type": "tool_use", "id": "tool_abc123", "name": "get_weather", "input": { "city": "Paris" } } ``` #### Tool Result Block (in user messages) ```json theme={null} { "type": "tool_result", "tool_use_id": "tool_abc123", "content": "The weather in Paris is sunny, 22 C" } ``` ### Tool Definitions ```json theme={null} { "tools": [ { "name": "get_weather", "description": "Get current weather for a city", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "City name" } }, "required": ["city"] } } ] } ``` ### Tool Choice Options | Value | Description | | --------------------------------------- | ---------------------------------- | | `"auto"` | Model may use tools if appropriate | | `"none"` | Disable tool use for this request | | `"any"` | Model must use at least one tool | | `"required"` | Model must use at least one tool | | `{"type": "tool", "name": "tool_name"}` | Force use of a specific tool | ### Extended Thinking For models that support extended thinking (reasoning): ```json theme={null} { "thinking": { "type": "enabled", "budget_tokens": 8192 } } ``` Requirements: * `budget_tokens` must be >= 1024 * `budget_tokens` must be \< `max_tokens` * Model must support thinking for the exact model ID you send (check `GET /api/v1/models`) `:thinking` is model-specific and only works when that exact ID (or a documented alias) exists. `-thinking` is a legacy alias pattern for some model families only, not universal. Do not assume `-thinking` works for arbitrary model IDs. Always check `GET /api/v1/models` for exact valid IDs. If the requested model does not support thinking, NanoGPT automatically ignores/strips the `thinking` parameter and routes the request to the base model. For Chat Completions compatibility controls, `:reasoning-exclude` (or `reasoning.exclude`) only hides reasoning output; it does not force reasoning compute off. Use `reasoning_effort` / `reasoning.effort` to control reasoning depth, and set `none` to disable reasoning behavior. ## Response Format ### Non-Streaming Response ```json theme={null} { "id": "msg_abc123", "type": "message", "role": "assistant", "model": "claude-opus-4-5-20251101", "content": [ { "type": "text", "text": "Hello! How can I help you today?" } ], "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 10, "output_tokens": 12, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "service_tier": "standard" } } ``` ### Stop Reasons | Stop Reason | Description | | ---------------- | -------------------------- | | `end_turn` | Natural end of response | | `max_tokens` | Hit token limit | | `stop_sequence` | Hit a custom stop sequence | | `tool_use` | Model wants to use a tool | | `content_filter` | Content was filtered | ### Streaming Response (SSE) See also: [Streaming Protocol (SSE)](/api-reference/miscellaneous/streaming-protocol). When `stream: true`, the response is Server-Sent Events with named event types: #### Event: message\_start ```text theme={null} event: message_start data: {"type": "message_start", "message": {"id": "msg_abc", "type": "message", "role": "assistant", "model": "claude-opus-4-5-20251101", "content": [], "stop_reason": null, "usage": {"input_tokens": 10, "output_tokens": 0}}} ``` #### Event: content\_block\_start ```text theme={null} event: content_block_start data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}} ``` #### Event: content\_block\_delta ```text theme={null} event: content_block_delta data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Hello"}} ``` #### Event: content\_block\_stop ```text theme={null} event: content_block_stop data: {"type": "content_block_stop", "index": 0} ``` #### Event: message\_delta ```text theme={null} event: message_delta data: {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"output_tokens": 12}} ``` #### Event: message\_stop ```text theme={null} event: message_stop data: {"type": "message_stop"} ``` ### Streaming Tool Use When the model uses tools during streaming: ```text theme={null} event: content_block_start data: {"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "tool_abc", "name": "get_weather", "input": {}}} event: content_block_delta data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"city\":"}} event: content_block_delta data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": " \"Paris\"}"}} event: content_block_stop data: {"type": "content_block_stop", "index": 0} ``` ## Supported Models ### Claude Models (Full Support) | Model | Streaming | Tools | Vision | Thinking | | -------------------------- | --------- | ----- | ------ | -------- | | claude-opus-4-6 | Yes | Yes | Yes | Yes\* | | claude-opus-4-5-20251101 | Yes | Yes | Yes | Yes\* | | claude-opus-4-1-20250805 | Yes | Yes | Yes | Yes\* | | claude-sonnet-4-5-20250929 | Yes | Yes | Yes | Yes\* | \*Use only exact thinking-capable model IDs from `GET /api/v1/models`. ### Other Models (Via Compatibility Layer) The v1/messages endpoint also works with non-Anthropic models: | Model | Streaming | Tools | Vision | | ----------------------------- | --------- | ----- | ------ | | Yes | Yes | Yes | | | google/gemini-3-flash-preview | Yes | Yes | Yes | | google/gemini-3.1-pro-preview | Yes | Yes | Yes | | zai-org/glm-4.7 | Yes | Yes | -- | See the [Models documentation](/api-reference/endpoint/models) for the full list. ## Prompt Caching For the full guide (supported models, thresholds, pricing, and usage fields), see [Prompt Caching](/api-reference/miscellaneous/prompt-caching). NanoGPT automatically applies implicit caching on providers/models that support it (including OpenAI, Gemini, and many open-source provider/model routes), with no extra request flags. Use explicit prompt-caching controls on Claude when you need deterministic cache boundaries, TTL selection, or `stickyProvider` consistency control. ### Enable via Header ``` anthropic-beta: prompt-caching-2024-07-31 ``` ### TTL Options * Default: 5-minute cache TTL * Extended: Add `extended-cache-ttl-2025-04-11` to request 1-hour TTL on Anthropic-native Claude flows ### Cache Control in Content (Explicit Claude Controls) Add `cache_control` to content blocks for explicit Claude caching: ```json theme={null} { "type": "text", "text": "This is a long system prompt...", "cache_control": { "type": "ephemeral" } } ``` ### Cache Usage in Response ```json theme={null} { "usage": { "input_tokens": 100, "output_tokens": 50, "cache_creation_input_tokens": 80, "cache_read_input_tokens": 0 } } ``` ## Error Handling For a general guide across NanoGPT APIs, see [Error Handling](/api-reference/miscellaneous/error-handling). ### Error Response Format ```json theme={null} { "type": "error", "error": { "type": "invalid_request_error", "message": "max_tokens is required", "param": "max_tokens" } } ``` ### Error Types | HTTP Status | Error Type | Description | | ----------- | ----------------------- | -------------------------------------------- | | 400 | `invalid_request_error` | Invalid request (missing fields, bad format) | | 401 | `authentication_error` | Invalid or missing API key | | 403 | `permission_error` | Insufficient permissions | | 404 | `not_found_error` | Unknown model | | 429 | `rate_limit_error` | Rate limit exceeded | | 500+ | `api_error` | Server error | All error responses include an `X-Request-ID` header for support requests. ## Headers ### Request Headers | Header | Required | Description | | ------------------- | -------- | ------------------------------------------- | | `Authorization` | Yes\* | Bearer token authentication | | `x-api-key` | Yes\* | Alternative API key header | | `Content-Type` | Yes | Must be `application/json` | | `anthropic-beta` | No | Enable beta features (e.g., prompt caching) | | `anthropic-version` | No | API version (accepted but not required) | \*One of `Authorization` or `x-api-key` is required. ### BYOK Headers For Bring Your Own Key: | Header | Description | | ----------------- | ------------------------------------- | | `x-use-byok` | Set to `true` to use your own API key | | `x-byok-provider` | Provider name for your key | ## Examples ### Basic Request (cURL) ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/messages \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "claude-opus-4-5-20251101", "max_tokens": 256, "messages": [ { "role": "user", "content": "Hello!" } ] }' ``` ### Streaming Request (cURL) ```bash theme={null} curl -N -X POST https://nano-gpt.com/api/v1/messages \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "claude-opus-4-5-20251101", "stream": true, "max_tokens": 256, "messages": [{ "role": "user", "content": "Hello!" }] }' ``` ### With Tools (cURL) ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/messages \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "claude-opus-4-5-20251101", "max_tokens": 1024, "tools": [ { "name": "get_weather", "description": "Get current weather", "input_schema": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } ], "messages": [ { "role": "user", "content": "What is the weather in Tokyo?" } ] }' ``` ### Anthropic SDK (Node.js) ```typescript theme={null} import Anthropic from "@anthropic-ai/sdk"; const anthropic = new Anthropic({ apiKey: process.env.NANOGPT_API_KEY, baseURL: "https://nano-gpt.com/api" }); const message = await anthropic.messages.create({ model: "claude-opus-4-5-20251101", max_tokens: 256, messages: [ { role: "user", content: "Hello!" } ] }); console.log(message.content[0].text); ``` ### Anthropic SDK with Streaming (Node.js) ```typescript theme={null} import Anthropic from "@anthropic-ai/sdk"; const anthropic = new Anthropic({ apiKey: process.env.NANOGPT_API_KEY, baseURL: "https://nano-gpt.com/api" }); const stream = await anthropic.messages.stream({ model: "claude-opus-4-5-20251101", max_tokens: 256, messages: [ { role: "user", content: "Tell me a story" } ] }); for await (const event of stream) { if (event.type === "content_block_delta" && event.delta.type === "text_delta") { process.stdout.write(event.delta.text); } } ``` ### Anthropic SDK with Prompt Caching (Node.js) ```typescript theme={null} import Anthropic from "@anthropic-ai/sdk"; const anthropic = new Anthropic({ apiKey: process.env.NANOGPT_API_KEY, baseURL: "https://nano-gpt.com/api" }); const message = await anthropic.messages.create({ model: "claude-opus-4-5-20251101", max_tokens: 256, system: [ { type: "text", text: "You are a helpful assistant with expertise in...", cache_control: { type: "ephemeral" } } ], messages: [ { role: "user", content: "Hello!" } ] }, { headers: { "anthropic-beta": "prompt-caching-2024-07-31" } }); ``` ### Vision Example (Node.js) ```typescript theme={null} import Anthropic from "@anthropic-ai/sdk"; import fs from "fs"; const anthropic = new Anthropic({ apiKey: process.env.NANOGPT_API_KEY, baseURL: "https://nano-gpt.com/api" }); const imageData = fs.readFileSync("image.jpg").toString("base64"); const message = await anthropic.messages.create({ model: "claude-opus-4-5-20251101", max_tokens: 1024, messages: [ { role: "user", content: [ { type: "text", text: "What's in this image?" }, { type: "image", source: { type: "base64", media_type: "image/jpeg", data: imageData } } ] } ] }); ``` ### Python SDK ```python theme={null} import anthropic client = anthropic.Anthropic( api_key="YOUR_NANOGPT_API_KEY", base_url="https://nano-gpt.com/api" ) message = client.messages.create( model="claude-opus-4-5-20251101", max_tokens=256, messages=[ {"role": "user", "content": "Hello!"} ] ) print(message.content[0].text) ``` ## Limits | Limit | Value | | ------------------ | ---------------------- | | Request timeout | 800 seconds | | Tool argument size | \~100 KB per tool call | | Image types | JPEG, PNG, GIF, WebP | ## Limitations * GPU-TEE models do not support streaming through `POST /api/v1/messages`. Use `POST /api/v1/chat/completions` if you need streaming with GPU-TEE models. ## Migration from Anthropic To migrate from Anthropic's API to NanoGPT: 1. Change the base URL: * From: `https://api.anthropic.com` * To: `https://nano-gpt.com/api` The full endpoint will be: `https://nano-gpt.com/api/v1/messages` 2. Use your NanoGPT API key instead of your Anthropic key 3. No other code changes required -- the API is fully compatible ## Service tier compatibility Anthropic-style service tiers are normalized when routing to providers that support service tiers: * `standard` → `default` * `default` → `default` * `flex` → `flex` * `priority` → `priority` * `batch` → ignored for service-tier routing Flex and priority availability is model- and provider-specific. If you explicitly force a provider that does not support service tiers, the requested tier may be ignored by the upstream provider, or routing and pricing may differ from the default route. ## Notes * The `anthropic-version` header is accepted but not required * Token usage numbers use NanoGPT's token accounting (may differ slightly from Anthropic's exact counts) * All Anthropic SDK features are supported, including streaming, tools, and caching # Messages Count Tokens Source: https://docs.nano-gpt.com/api-reference/endpoint/messages-count-tokens POST https://nano-gpt.com/api/v1/messages/count_tokens Anthropic-compatible token estimate endpoint for planning and cost control ## Overview Use `POST /api/v1/messages/count_tokens` to estimate the number of input tokens for an Anthropic Messages-format request. This endpoint is useful for request planning, context budgeting, and cost control. The result is an estimate and may not exactly match a provider tokenizer. ## Endpoint ```text theme={null} POST https://nano-gpt.com/api/v1/messages/count_tokens ``` ## Authentication Use either header: * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Request Body ```json theme={null} { "model": "claude-3-5-sonnet", "system": "You are concise.", "messages": [ { "role": "user", "content": "Hello" } ], "tools": [], "tool_choice": null } ``` ## Supported Content The request uses Anthropic message format and supports: * text blocks * image and document-like blocks * tool use blocks * tool result blocks * `system` * `tools` * `tool_choice` ## Response ```json theme={null} { "input_tokens": 123 } ``` ## Example ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/messages/count_tokens \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-3-5-sonnet", "system": "You are concise.", "messages": [ { "role": "user", "content": "Hello" } ] }' ``` ## Notes * This is an estimate for planning and cost control. * It may not exactly match a provider tokenizer. * For generation, use `POST /api/v1/messages`. # Models Source: https://docs.nano-gpt.com/api-reference/endpoint/models GET /v1/models List available models with optional detailed information including pricing ## Overview The `/api/v1/models` endpoint provides a list of available text generation models. It supports optional detailed information including pricing data. The endpoint maintains full backwards compatibility while adding powerful new features. For non-text model catalogs, use the dedicated catalog endpoints: * [Image models](/api-reference/endpoint/image-models) * [Video models](/api-reference/endpoint/video-models) * [Audio models](/api-reference/endpoint/audio-models) * [Embedding models](/api-reference/endpoint/embedding-models) For API-only distillation metadata and filtering, see [Distillation Policy](/api-reference/miscellaneous/distillation-policy). ## Notable Model IDs (Examples) Model availability changes frequently. Use `GET /api/v1/models` as the authoritative source of callable IDs. Examples below use canonical IDs only; legacy aliases are intentionally omitted. * **OpenAI**: * **Anthropic**: `anthropic/claude-opus-4.6`, `anthropic/claude-opus-4.6:thinking`, `anthropic/claude-sonnet-4.6`, `anthropic/claude-sonnet-4.6:thinking` * **Google Gemini 3**: `google/gemini-3-flash-preview`, `google/gemini-3.1-pro-preview`, `gemini-3-pro-image-preview`, `google/gemini-3-flash-preview-thinking` * **xAI Grok**: `x-ai/grok-4.3`, `x-ai/grok-latest`, `x-ai/grok-4.20`, `x-ai/grok-4.20-multi-agent` * **Moonshot Kimi K2.5**: `moonshotai/kimi-k2.5`, `moonshotai/kimi-k2.5:thinking` * **Zhipu GLM 4.6 / 4.7**: `z-ai/glm-4.6`, `z-ai/glm-4.6:thinking`, `zai-org/glm-4.7`, `zai-org/glm-4.7:thinking` * **DeepSeek V3.2**: `deepseek/deepseek-v3.2`, `deepseek/deepseek-v3.2:thinking` * **Qwen3 Coder**: `qwen/qwen3-coder-next`, `qwen/qwen3-coder-plus`, `qwen/qwen3-coder-flash` * **NousResearch Hermes 4**: `nousresearch/hermes-4-405b`, `nousresearch/hermes-4-405b:thinking`, `nousresearch/hermes-4-70b` ## Compatibility Responses mirror OpenAI's Models API shape. All models endpoints return: ```json theme={null} { "object": "list", "data": [ { /* model */ }, ... ] } ``` Each model minimally includes: ```json theme={null} { "id": "openai/gpt-5.6-sol", "object": "model", "created": 1736966400, "owned_by": "openai" } ``` ## Features * **Basic Mode**: Standard OpenAI-compatible model listing * **Detailed Mode**: Enhanced information with pricing and model descriptions ## Provider Selection Discovery Provider selection support and provider IDs are discovered via `GET /api/models/:canonicalId/providers`. See [Provider Selection](/api-reference/miscellaneous/provider-selection) for details. ## Query Parameters | Parameter | Type | Default | Description | | ---------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `detailed` | boolean | `false` | Returns detailed model information including pricing and capabilities | | `sort` | string | none | Values: `favorites`, `mostused`. Reorders the returned model list. Sorting only affects models that would already be visible to the caller. It does not add or hide models. | When `detailed=true`, additional fields may be included per model: * `name` — display name * `description` — short model description * `context_length` — max input tokens (if known) * `max_output_tokens` — max output tokens (if known) * `capabilities` — feature flags (see below) * `pricing.prompt` and `pricing.completion` — at-cost per-million-token pricing in USD * `pricing.unit` — `per_million_tokens` * `icon_url` — small icon representing the provider * `cost_estimate` — internal rollup used in UI for cost hints (for example `"$"`, `"$$"`, or an object) * `category` — optional category tag (for example `"flagship"`, `"mini"`, `"reasoning"`) * `distillationPolicy` — optional policy metadata indicating whether model outputs may be used for distillation or output-based training under NanoGPT's recorded model-license rules ### Discovering video-capable models For multimodal input, call `GET /api/v1/models?detailed=true` and select a model whose catalog entry advertises video input. Catalog fields are additive, so inspect both the architecture modalities and capability flags when present: ```json theme={null} { "id": "google/gemini-3.1-flash-lite", "architecture": { "input_modalities": ["text", "image", "video"] }, "capabilities": { "video_input": true } } ``` An explicitly selected model without video capability is rejected before provider dispatch and billing. Automatic fallback keeps only routes that preserve the requested video input. See [Video Input](/api-reference/miscellaneous/video-input) for the endpoint content shapes and source rules. ### Sorting model lists `GET /api/v1/models` supports `sort=favorites` and `sort=mostused`. When `sort` is present, NanoGPT returns the same model list the caller would normally receive, but reordered by the selected ranking. Sorting only affects models that would already be visible to the caller. It does not add or hide models. * `sort=favorites`: Orders models by the authenticated account's recent model usage. * `sort=mostused`: Orders models by global completed model usage. * Unused or unranked models remain in the response after ranked models, in the normal catalog order. * `sort=favorites` requires a valid API key to personalize the order. If no valid API key is provided, it falls back to normal ordering. * `sort=mostused` does not require authentication. * Invalid `sort` values are ignored. * Can be combined with `detailed=true`. Examples: ```bash theme={null} curl -H "Authorization: Bearer $NANOGPT_API_KEY" \ "https://nano-gpt.com/api/v1/models?sort=favorites" ``` ```bash theme={null} curl "https://nano-gpt.com/api/v1/models?sort=mostused" ``` ```bash theme={null} curl -H "Authorization: Bearer $NANOGPT_API_KEY" \ "https://nano-gpt.com/api/v1/models?detailed=true&sort=favorites" ``` ```bash theme={null} curl "https://nano-gpt.com/api/v1/models?detailed=true&sort=mostused" ``` ### `capabilities` (detailed mode) Common capability flags: | Field | Type | Description | | --------------------- | ------- | ---------------------------------------------------------- | | `vision` | boolean | Supports image inputs | | `reasoning` | boolean | Supports extended thinking/reasoning | | `tool_calling` | boolean | Supports function/tool calling | | `parallel_tool_calls` | boolean | Supports multiple tool calls in parallel | | `structured_output` | boolean | Supports structured/JSON output modes | | `pdf_upload` | boolean | Supports PDF/document inputs | | `video_input` | boolean | Supports video inputs for text or multimodal understanding | ## Authentication Authentication is optional but enables user-specific pricing in detailed mode: | Header | Format | Required | Description | | --------------- | ------------------ | -------- | --------------------------------- | | `Authorization` | `Bearer {api_key}` | Optional | API key for user-specific pricing | | `x-api-key` | `{api_key}` | Optional | Alternative API key header | Notes: * Invalid or missing API keys still return a list of models. We simply omit user-specific pricing considerations in `detailed=true` mode. * With a valid key, the canonical `/api/v1/models` may apply your account’s subscription visibility preference (see Endpoint Variants). ## Response Formats ### Basic Response (Default) Standard OpenAI-compatible format without pricing information: ```json theme={null} { "object": "list", "data": [ { "id": "openai/gpt-5.6-sol", "object": "model", "created": 1704067200, "owned_by": "openai" }, { "id": "anthropic/claude-opus-4.6", "object": "model", "created": 1704067200, "owned_by": "anthropic" } ] } ``` ### Detailed Response Enhanced format with model descriptions, context lengths, and pricing: ```json theme={null} { "object": "list", "data": [ { "id": "openai/gpt-5.6-sol", "object": "model", "created": 1704067200, "owned_by": "openai", "name": "GPT-5.2", "description": "OpenAI's flagship general-purpose model", "context_length": 128000, "max_output_tokens": 16384, "capabilities": { "vision": true, "reasoning": false, "tool_calling": true, "parallel_tool_calls": true, "structured_output": true, "pdf_upload": true, "video_input": true }, "pricing": { "prompt": 2.50, "completion": 10.00, "currency": "USD", "unit": "per_million_tokens" }, "icon_url": "/icons/OpenAI.svg", "cost_estimate": "$", "category": "flagship" }, { "id": "anthropic/claude-opus-4.6", "object": "model", "created": 1704067200, "owned_by": "anthropic", "name": "Claude Opus 4.5", "description": "Anthropic's top-tier model for deep reasoning and writing", "context_length": 200000, "max_output_tokens": 8192, "capabilities": { "vision": true, "reasoning": true, "tool_calling": true }, "pricing": { "prompt": 15.00, "completion": 75.00, "currency": "USD", "unit": "per_million_tokens" }, "icon_url": "/icons/Anthropic.svg", "cost_estimate": "$$$", "category": "reasoning" } ] } ``` ## Field Descriptions ### Basic Fields (Always Present) | Field | Type | Description | | ---------- | ------ | ------------------------------------------------------ | | `id` | string | Unique model identifier | | `object` | string | Always "model" for OpenAI compatibility | | `created` | number | Unix timestamp of response creation | | `owned_by` | string | Model provider (openai, anthropic, meta, google, etc.) | ### Enhanced Fields (Detailed Mode Only) | Field | Type | Description | | -------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------- | | `name` | string | Human-readable model name | | `description` | string | Detailed model description | | `context_length` | number | Maximum input tokens (null if not available) | | `max_output_tokens` | number | Maximum output tokens (null if not available) | | `capabilities` | object | Feature flags (e.g., `vision: boolean`) | | `pricing` | object | Pricing information object | | `icon_url` | string | Path/URL for a small provider icon | | `cost_estimate` | string or object | Internal hints (for example `"$"` / `"$$"` or `{ cheap: true }`) | | `category` | string | Optional category (e.g., `"flagship"`, `"mini"`, `"reasoning"`) | | `distillationPolicy` | object | Optional API-only distillation metadata. See [Distillation Policy](/api-reference/miscellaneous/distillation-policy). | ### Pricing Object Structure | Field | Type | Description | | ------------ | ------ | ------------------------------------- | | `prompt` | number | Cost per million input tokens in USD | | `completion` | number | Cost per million output tokens in USD | | `currency` | string | Always "USD" | | `unit` | string | Always "per\_million\_tokens" | ### Distillation Policy Where available, text model records include a `distillationPolicy` object with `status`, `label`, `basis`, `sourceUrl`, and `note`. This indicates whether model outputs may be used for model training, fine-tuning, distillation, or similar model-improvement workflows under NanoGPT's recorded interpretation of model licenses and provider terms. ```json theme={null} { "distillationPolicy": { "status": "allowed", "label": "License permits distillation", "basis": "permissive-open-weights", "sourceUrl": "https://example.com/license-or-terms", "note": "Short explanation of the policy signal." } } ``` Use the explore endpoints when you need to filter for distillation-allowed text models: ```http theme={null} GET /api/explore/text-models?distillation=allowed ``` ```http theme={null} GET /api/explore/search?type=text&q=qwen&distillation=allowed ``` The filter applies only to text models. Responses include `meta.distillation: "allowed"` when the filter is used and `meta.distillation: "all"` otherwise. Provider route policy can differ from model-level policy. Use `GET /api/models/:canonicalId/providers` and inspect each provider row's `distillationPolicy` before relying on a specific route for distillation. This metadata is informational and is not legal advice. Provider terms and model licenses can change, and licenses may impose attribution, naming, acceptable-use, or derivative-model restrictions even when distillation is allowed. ## Usage Examples ### Basic Request ```bash theme={null} curl "https://nano-gpt.com/api/v1/models" ``` ### Detailed Request ```bash theme={null} curl "https://nano-gpt.com/api/v1/models?detailed=true" ``` ### Sort by Favorites ```bash theme={null} curl -H "Authorization: Bearer $NANOGPT_API_KEY" \ "https://nano-gpt.com/api/v1/models?sort=favorites" ``` ### Sort by Most Used ```bash theme={null} curl "https://nano-gpt.com/api/v1/models?sort=mostused" ``` ### Detailed and Sorted ```bash theme={null} curl -H "Authorization: Bearer $NANOGPT_API_KEY" \ "https://nano-gpt.com/api/v1/models?detailed=true&sort=favorites" ``` ```bash theme={null} curl "https://nano-gpt.com/api/v1/models?detailed=true&sort=mostused" ``` ### Detailed with Authentication ```bash theme={null} curl "https://nano-gpt.com/api/v1/models?detailed=true" \ -H "Authorization: Bearer your_api_key_here" ``` ### Alternative API Key Header ```bash theme={null} curl "https://nano-gpt.com/api/v1/models?detailed=true" \ -H "x-api-key: your_api_key_here" ``` ## Endpoint Variants In addition to the canonical `/api/v1/models`, two filtered variants are available: ### 1) GET /api/v1/models (canonical) * Returns all visible text models (excludes internal free/helper selector models except `auto-model*`). * If your account has an active subscription and you have not enabled “Also show paid models”, the list is automatically restricted to only subscription-included models. * If you enable “Also show paid models” in settings, it returns the full set again. Examples: ```bash theme={null} curl -H "Authorization: Bearer $NANOGPT_API_KEY" \ https://nano-gpt.com/api/v1/models ``` ```bash theme={null} curl -H "Authorization: Bearer $NANOGPT_API_KEY" \ "https://nano-gpt.com/api/v1/models?detailed=true" ``` ### 2) GET /api/subscription/v1/models (subscription-only) * Always returns only models included in the NanoGPT subscription (equivalent to our `isTextEligible(modelId)` filter). * Ignores the user’s “Also show paid models” preference; it is always subscription-only. * Supports `?detailed=true` and API key–aware, at-cost pricing metadata. Examples: ```bash theme={null} curl https://nano-gpt.com/api/subscription/v1/models ``` ```bash theme={null} curl -H "x-api-key: $NANOGPT_API_KEY" \ "https://nano-gpt.com/api/subscription/v1/models?detailed=true" ``` ### 3) GET /api/paid/v1/models (paid/extras) * Returns only models that are NOT part of the subscription (paid/premium/extras). * Supports `?detailed=true` and API key–aware, at-cost pricing metadata. Examples: ```bash theme={null} curl https://nano-gpt.com/api/paid/v1/models ``` ```bash theme={null} curl -H "Authorization: Bearer $NANOGPT_API_KEY" \ "https://nano-gpt.com/api/paid/v1/models?detailed=true" ``` ### Choosing the Right Endpoint * Use `/api/subscription/v1/models` for curated lists guaranteed to be subscription-included (e.g., sub-only integrations). * Use `/api/paid/v1/models` to focus on paid or premium models. * Use `/api/v1/models` for the canonical list and let the account’s “Also show paid models” preference decide visibility. ## Errors and Limits * These endpoints typically return `200` with a list. If an invalid API key is provided, the list still returns and simply omits user-specific pricing considerations in `detailed=true` mode. * Standard CORS and rate limiting apply. In overload scenarios you may receive `429` with: ```json theme={null} { "code": "rate_limited", "message": "Rate limit exceeded" } ``` ## Backwards Compatibility * Default response format unchanged * All existing fields preserved * New fields are additive only * No breaking changes to existing integrations ## Related Endpoints * [Image models](/api-reference/endpoint/image-models) - List image model capabilities and supported parameters * [Video models](/api-reference/endpoint/video-models) - List video model capabilities and supported parameters * [Audio models](/api-reference/endpoint/audio-models) - List text-to-speech and speech-to-text models * [Embedding models](/api-reference/endpoint/embedding-models) - List embedding model dimensions, token limits, and pricing * [/api/v1/embeddings](/api-reference/endpoint/embeddings) - Create embeddings using the available embedding models * [Provider Selection](/api-reference/miscellaneous/provider-selection) - Discover model providers and set provider preferences * `/api/subscription/v1/models` - Subscription-included text models * `/api/paid/v1/models` - Paid or premium text models ## Notes * Scope: These endpoints list text-chat models. For image, video, audio, and embedding catalogs, use the dedicated model catalog pages linked above. * Fields are subject to change as new capabilities or providers are added. We aim to remain OpenAI-API compatible for basic consumers (`id`/`object`/`created`/`owned_by`). # Moderation Models Source: https://docs.nano-gpt.com/api-reference/endpoint/moderation-models GET /v1/moderation-models List available content moderation models ## Overview Use `GET /api/v1/moderation-models` to list the moderation models currently available to your account. The response includes model capabilities, context limits, and pricing. Model availability and capabilities can change over time. Use this endpoint as the source of truth before choosing a model for `POST /api/v1/moderations` or [Inline Moderation](/api-reference/miscellaneous/inline-moderation). ## Endpoint ```text theme={null} GET https://nano-gpt.com/api/v1/moderation-models ``` ## Authentication An API key is required. * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Query Parameters | Parameter | Type | Default | Description | | ---------- | ------- | ------- | -------------------------------------------------------- | | `detailed` | boolean | `true` | When `false`, returns a compact OpenAI-style model list. | ## Examples ```bash theme={null} curl https://nano-gpt.com/api/v1/moderation-models \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ```bash theme={null} curl "https://nano-gpt.com/api/v1/moderation-models?detailed=false" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ## Response The detailed response returns a model list with capability and pricing metadata. Exact fields can vary by model. ```json theme={null} { "object": "list", "data": [ { "id": "moderation-model-id", "object": "model", "created": 1704067200, "owned_by": "model-owner", "context_length": 128000, "capabilities": { "text": true, "image": true, "batch": true }, "pricing": { "prompt": 0.10, "completion": 0.00, "currency": "USD", "unit": "per_million_tokens" } } ] } ``` ## Notes * This is a paid API feature. * Some moderation models support text only; others support both text and image inputs. * Batch support can vary by model. * Pricing details are returned by this endpoint and can vary by selected model. # Moderations Source: https://docs.nano-gpt.com/api-reference/endpoint/moderations POST /v1/moderations Classify text and image content for safety ## Overview Use `POST /api/v1/moderations` to classify text, image, or mixed content for safety before you store, display, or send user-generated content to another model endpoint. If you want NanoGPT to run a paid input safety preflight automatically before supported generation requests, use [Inline Moderation](/api-reference/miscellaneous/inline-moderation) instead. The endpoint is synchronous and returns an OpenAI-compatible moderation response shape where practical. It is a paid API feature and bills proportionally to usage with no separate minimum charge. ## Endpoint ```text theme={null} POST https://nano-gpt.com/api/v1/moderations ``` ## Authentication An API key is required. * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Model Selection The `model` field is optional. If omitted, NanoGPT uses the default moderation model. Available input types, batch support, context limits, and pricing depend on the selected model. Use [Moderation Models](/api-reference/endpoint/moderation-models) before choosing a model instead of hardcoding capabilities. ## Request Body | Field | Type | Required | Description | | ------- | ------------------------------- | -------- | -------------------------------------------------------------- | | `model` | string | No | Moderation model ID. Omit to use the default moderation model. | | `input` | string, array, or content parts | Yes | Content to classify. | ## Input Examples ### Single Text Input ```json theme={null} { "model": "moderation-model-id", "input": "Text to classify." } ``` ### Batch Text Input ```json theme={null} { "model": "moderation-model-id", "input": [ "First item to classify.", "Second item to classify." ] } ``` Batch support can vary by model. If a selected model does not support batched input, the API returns an error before billing. ### Text Content Part ```json theme={null} { "model": "moderation-model-id", "input": [ { "type": "text", "text": "Text to classify." } ] } ``` ### Image Input ```json theme={null} { "model": "moderation-model-id", "input": [ { "type": "image_url", "image_url": { "url": "https://example.com/image.png" } } ] } ``` Image support depends on the selected model. Text-only models reject image input before billing. ## Example ```bash theme={null} curl https://nano-gpt.com/api/v1/moderations \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "moderation-model-id", "input": "Text to classify for safety." }' ``` ## Response ```json theme={null} { "id": "modr_...", "model": "moderation-model-id", "results": [ { "flagged": false, "categories": { "category_name": false }, "category_scores": { "category_name": 0.01 } } ], "usage": { "prompt_tokens": 12, "completion_tokens": 100, "total_tokens": 112 } } ``` | Field | Description | | ----------------- | ------------------------------------------------------------------------------------------ | | `flagged` | `true` when the selected model classifies the content as unsafe. | | `categories` | Category-level boolean results. Exact category names are model-dependent. | | `category_scores` | Category-level confidence scores when available. Exact category names are model-dependent. | | `usage` | Token usage used for billing and diagnostics. | ## Billing The endpoint is paid and bills proportionally to usage. There is no extra minimum charge. General billing behavior: * Input tokens are billed according to the selected moderation model's input rate. * Output tokens may be billed when the selected model returns billable classification output. * NanoGPT performs a preflight balance check before dispatching the request. * The final charge is based on measured or estimated usage recorded for the completed request. * Pricing details are returned by `GET /api/v1/moderation-models`. ## Rate Limits And Access Controls The endpoint uses standard NanoGPT API authentication, balance checks, API-key limits, and account/team access controls. * API keys can be subject to model allowlists. * API keys can be subject to spending and token limits. * Team billing and team restrictions apply where configured. * Requests can be rate limited. ## Errors Errors follow the standard NanoGPT/OpenAI-compatible error shape: ```json theme={null} { "error": { "message": "Invalid request parameters. Please check your input and try again.", "type": "invalid_request_error", "code": "unsupported_input_modality", "param": "input" } } ``` | Status | Code | Meaning | | ------ | -------------------------------------- | -------------------------------------------------------------------------------------------- | | 400 | `empty_input` | The request did not include moderatable text or image content. | | 400 | `unsupported_input_modality` | The selected model does not support the submitted input type. | | 400 | `unsupported_batch_input` | The selected model does not support batched input. | | 400 | `context_length_exceeded` | The input is too large for the selected model. | | 401 | `missing_api_key` or `invalid_api_key` | Authentication failed. | | 402 | varies | The account does not have enough balance. | | 403 | `paid_features_disabled` | Paid API features are disabled for the account. | | 404 | `model_not_found` | The requested moderation model does not exist or is unavailable. | | 429 | `rate_limit_exceeded` | The caller exceeded a rate limit. | | 503 | `provider_error` or similar | The upstream moderation service was temporarily unavailable or returned an invalid response. | # NSFW Image Classification Source: https://docs.nano-gpt.com/api-reference/endpoint/nsfw-image POST /nsfw/image Binary NSFW classification for up to 10 image URLs or data URLs per request ## Overview The NSFW Image Classification endpoint performs **binary classification** on image URLs (or base64 data URLs) and returns whether each image contains NSFW concepts. Send up to **10 images per request**. ## Authentication Include your API key in the request header: ``` x-api-key: YOUR_API_KEY ``` Alternatively, you can use Bearer token authentication: ``` Authorization: Bearer YOUR_API_KEY ``` ## Request ### Headers ``` Content-Type: application/json x-api-key: YOUR_API_KEY ``` ### Body parameters You may provide images in **any** of the following fields. The endpoint accepts **up to 10 images per request**. | Parameter | Type | Required | Description | | --------------- | ------------------- | -------- | ---------------------------------------------------- | | `image_urls` | string \| string\[] | No | Primary input field. One URL or an array of URLs. | | `imageUrls` | string \| string\[] | No | Alias for `image_urls`. | | `imageUrl` | string | No | Alias for a single image URL. | | `imageDataUrl` | string | No | Base64 data URL for a single image. | | `imageDataUrls` | string\[] | No | Base64 data URLs for multiple images. | | `model` | string | No | Only supported value is `nsfw-classifier` (default). | At least one image field is required. If more than 10 images are provided, only the **first 10** are processed and billed. ### Example request ```json theme={null} { "image_urls": [ "https://example.com/image-1.jpg", "https://example.com/image-2.jpg" ] } ``` ## Response ### Success (200) ```json theme={null} { "model": "nsfw-classifier", "requestId": "", "inputCount": 2, "cost": 0.003, "currency": "USD", "truncated": false, "has_nsfw_concepts": [false, true], "is_nsfw": true } ``` ### Response fields * `model`: The classifier model used. * `requestId`: Request ID (if available; helpful for support). * `inputCount`: Number of images processed (max 10). * `cost`: Final charged amount (after discounts and currency conversion). * `currency`: `USD` or `XNO` depending on payment source. * `truncated`: `true` if more than 10 images were provided. * `has_nsfw_concepts`: List of booleans (one per image, in input order). * `is_nsfw`: `true` if any image was flagged. ## Errors ### 400 – Invalid or policy violation * Missing or invalid image URLs * JSON parsing error * Policy violation (returns a safety error message) ```json theme={null} { "error": "No valid image URLs provided" } ``` ### 401 – Unauthorized ```json theme={null} { "error": "Unauthorized" } ``` ### 429 – Rate limit or API key usage limits ```json theme={null} { "error": "Rate limit exceeded" } ``` ### 500 – Server or provider failure ```json theme={null} { "error": "NSFW classification failed" } ``` ## Billing Notes * Charged **\$0.0015 per image** actually sent to the classifier. * If more than 10 images are provided, only the **first 10** are billed/processed. * Discounts and referral policies apply as with other endpoints. ## Code Examples ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/nsfw/image \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "image_urls": [ "https://example.com/image-1.jpg", "https://example.com/image-2.jpg" ] }' ``` ```python Python theme={null} import requests api_key = "YOUR_API_KEY" payload = { "image_urls": [ "https://example.com/image-1.jpg", "https://example.com/image-2.jpg" ] } response = requests.post( "https://nano-gpt.com/api/nsfw/image", headers={ "Content-Type": "application/json", "x-api-key": api_key }, json=payload ) print(response.json()) ``` ```javascript JavaScript theme={null} const apiKey = 'YOUR_API_KEY'; const response = await fetch('https://nano-gpt.com/api/nsfw/image', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey }, body: JSON.stringify({ image_urls: [ 'https://example.com/image-1.jpg', 'https://example.com/image-2.jpg' ] }) }); const data = await response.json(); console.log(data); ``` # Personalized Models Source: https://docs.nano-gpt.com/api-reference/endpoint/personalized-models GET /personalized/v1/models # Personalized Models API Curated, OpenAI‑compatible model listing scoped to each account’s preferences. This endpoint returns only the text models you have marked as “visible” in Settings → Models, regardless of whether they are subscription or paid models. * GET `/api/personalized/v1/models` See also: * Canonical models list: `GET /api/v1/models` * Subscription‑only list: `GET /api/subscription/v1/models` * Paid‑only list: `GET /api/paid/v1/models` ## Compatibility & Response Shape The response mirrors OpenAI’s Models API: ```json theme={null} { "object": "list", "data": [ { /* model */ }, ... ] } ``` Each model contains at least: ```json theme={null} { "id": "openai/gpt-5.6-sol", "object": "model", "created": 1736966400, "owned_by": "openai" } ``` Use `?detailed=true` to include additional fields like `name`, `description`, `context_length`, `capabilities`, `pricing`, `icon_url`, and `cost_estimate`. ## Authentication An API key is required. * `Authorization: Bearer ` * or `x-api-key: ` If the key is missing or invalid, the endpoint returns `401`. ## Personalization Rules * The list is filtered by your account’s visible text models (configured in the NanoGPT web app under Settings → Models → “Visible Text Models”). * This endpoint ignores the user preference “Also show paid models.” If you marked paid models as visible, they appear here even if you generally hide paid models elsewhere. * If you have not set any preferences, the endpoint falls back to NanoGPT defaults (`visible === true` in our model registry). ## Examples Basic list: ```bash theme={null} curl -H "Authorization: Bearer $NANOGPT_API_KEY" \ https://nano-gpt.com/api/personalized/v1/models ``` Detailed list with pricing/capabilities: ```bash theme={null} curl -H "x-api-key: $NANOGPT_API_KEY" \ "https://nano-gpt.com/api/personalized/v1/models?detailed=true" ``` Sample detailed item: ```json theme={null} { "id": "openai/gpt-5.6-sol", "object": "model", "created": 1736966400, "owned_by": "openai", "name": "GPT-5.2", "description": "OpenAI flagship general-purpose model", "context_length": 128000, "capabilities": { "vision": false }, "pricing": { "prompt": 2.50, "completion": 10.00, "currency": "USD", "unit": "per_million_tokens" }, "icon_url": "/icons/OpenAI.svg", "cost_estimate": { "cheap": false } } ``` ## Managing Your Visible Models The recommended way to customize your personalized list is via the NanoGPT web UI: * Open Settings → Models → “Visible Text Models”. * Toggle visibility and categories as needed; changes are saved to your account. Advanced (web session only): * GET `/api/user/model-visibility` — returns your saved preferences as `{ modelPreferences: { visibleTextModels, modelCategories } }`. * POST `/api/user/model-visibility` — upserts preferences. Cookie‑based session auth is required (not API‑key auth). Payload shape: ```json theme={null} { "visibleTextModels": { "openai/gpt-5.6-sol": true, "anthropic/claude-opus-4.5": false }, "modelCategories": { "openai/gpt-5.6-sol": "General" } } ``` Keys outside NanoGPT’s known model ids are ignored. ## Notes * This route is explicitly dynamic (no shared caching across keys). * Model ids and metadata evolve as providers update their catalogs; keep consumers resilient to new fields. * Personalized results may include paid models even if `/api/v1/models` hides them for your account (by design). # Receive Nano Source: https://docs.nano-gpt.com/api-reference/endpoint/receive-nano POST /receive-nano Process pending Nano transactions for the account # Responses Source: https://docs.nano-gpt.com/api-reference/endpoint/responses POST /v1/responses Create a response with the OpenAI-compatible Responses API. Compatible models can use the Responses-only hosted tool-search pilot by adding a nanogpt:tool_search or tool_search entry and marking function tools with defer_loading: true. The NanoGPT Advisor extension is available for non-streaming, foreground, platform-billed pay-as-you-go API-key requests that do not use client tools, structured output, inline moderation, BYOK, accountless payment, memory, or server-side content enhancements. `/v1/responses` accepts **compressed request bodies** (`Content-Encoding: gzip`, `deflate`, or `br`) on authenticated JSON requests — useful for long conversations, where compressed uploads cut time-to-first-token. See [Compressed Request Bodies](/api-reference/miscellaneous/request-compression). ## Overview The `/v1/responses` API is an OpenAI Responses API-compatible endpoint for creating AI model responses. It supports: * Stateless and stateful (conversation threading) chat completions * Streaming responses via Server-Sent Events (SSE) * Background (async) processing for long-running requests * Response storage and retrieval * Function/tool calling support * Multimodal inputs (images, video, files) for supported models **Recommended base URL:** Use `https://api.nano-gpt.com/api/v1` for Responses API clients. This dedicated API host accepts request bodies up to NanoGPT's 32 MiB application limit and avoids the smaller ingress limit on the website host. It is especially important for Codex and other long-running agents that resend accumulated conversation state. **Accountless x402 payments**: Non-streaming `POST /api/v1/responses` requests can be quoted without an account or API key on supported deployments when the initial quote request includes `x-x402: true`. Streaming and background Responses have implementation coverage but are not part of the stable public accountless contract. This endpoint supports accountless x402 payments where listed by `GET /api/v1/x402/endpoints`, including Lightning L402 when advertised. See [Accountless x402 API Payments](/api-reference/miscellaneous/x402) for the full flow. Provider selection is available for supported open-source models. `X-Provider` explicitly selects a provider for the request and is always billed pay-as-you-go at the selected provider's price, including provider-selection markup. For provider-selection-capable models, `model` may include routing preference suffixes such as `:fast` (alias for `:speed`) and `:cheap` (alias for `:price`). These are billed like explicit provider selection and follow the same conflict rules. For subscription users, sending `X-Provider` bypasses subscription coverage for that request; `X-Billing-Mode: paygo` is only needed when forcing pay-as-you-go without an explicit provider or when saved provider preferences should apply to subscription-included traffic. See [Provider Selection](/api-reference/miscellaneous/provider-selection), [Model Suffixes](/api-reference/miscellaneous/model-suffixes), and [Pay-As-You-Go Billing Override](/api-reference/miscellaneous/billing-override). **Advisor extension:** Non-streaming pay-as-you-go API-key requests can include an `advisor` object so the executor model can consult one different model before returning its final answer. Use `mode: "auto"` to let the executor decide or `mode: "required"` to require a consultation attempt. Each completed model phase is billed separately. Advisor is a NanoGPT extension, not part of the standard OpenAI request schema. See [Advisor](/api-reference/miscellaneous/advisor). **Hosted tool search:** Compatible models can discover relevant functions from large deferred catalogs. Add `nanogpt:tool_search` and mark functions with `defer_loading: true`. This pilot is Responses-only. See [Hosted tool search](/api-reference/miscellaneous/hosted-tool-search). ## Authentication Use an API key for normal authenticated billing: ``` Authorization: Bearer YOUR_API_KEY ``` Or alternatively: ``` x-api-key: YOUR_API_KEY ``` For API-key requests, you can optionally pass `x-team-id` to choose team context when team defaults are evaluated (for example, retention defaults). For supported accountless x402 requests, omit `Authorization` and `x-api-key`, and include `x-x402: true` to receive a payment quote. The advertised schemes, including Lightning L402 when enabled, are listed by `GET /api/v1/x402/endpoints`. If you receive `401 missing_api_key` immediately, check that the initial quote request includes `x-x402: true`. Without that header, NanoGPT does not enter the x402 quote flow. ## Endpoints * `POST /v1/responses` - Create a new response from the model * `GET /v1/responses` - Returns endpoint information * `GET /v1/responses/{id}` - Retrieve a stored response by ID * `DELETE /v1/responses/{id}` - Delete a stored response (soft delete) ## Batch processing For high-volume work that does not need an immediate response, `/v1/responses` requests can be submitted through the [Batch API](/api-reference/endpoint/batches). Responses batches are non-streaming, stateless, use direct OpenAI models, and run with `store: false`. Function and custom tools, structured text output, and remote or data-URL images are supported; video input remains unsupported in Responses Batch. Stateful features, provider-hosted tools, file references, and NanoGPT-only request extensions are not. ## BYOK Encryption (Stored Responses) If you set `store: true`, you can optionally encrypt the stored response at rest using your own key or passphrase. To encrypt a stored response, include one of these headers on `POST /v1/responses`: * `x-encryption-key: YOUR_ENCRYPTION_KEY` * `x-encryption-passphrase: YOUR_PASSPHRASE` When retrieving or deleting an encrypted response, include the same header you used at creation time. Example: ```bash theme={null} # Create an encrypted, stored response curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "x-encryption-key: YOUR_ENCRYPTION_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Sensitive information", "store": true }' # Retrieve it later (must include the same encryption header) curl https://api.nano-gpt.com/api/v1/responses/resp_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "x-encryption-key: YOUR_ENCRYPTION_KEY" ``` ## Create Response ### Request ```http theme={null} POST /v1/responses Content-Type: application/json Authorization: Bearer YOUR_API_KEY ``` ### Request Body | Parameter | Type | Required | Description | | ---------------------- | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `model` | string | Yes | Model ID to use for the response. Provider-selection-capable models may include routing preference suffixes such as `:fast`, `:speed`, `:cheap`, `:price`, `:latency`, `:throughput`, `:floor`, `:tools`, `:caching`, `:cache`, or `:cached`. | | `input` | string or array | Yes | The input prompt or array of input items | | `advisor` | object | No | NanoGPT extension that lets the executor consult one different, client-selected model. Initially limited to non-streaming, pay-as-you-go API-key requests. See [Advisor](/api-reference/miscellaneous/advisor). | | `instructions` | string | No | System instructions for the model | | `max_output_tokens` | integer | No | Maximum tokens in the response (minimum: 16) | | `max_tool_calls` | integer | No | Maximum number of tool calls allowed | | `temperature` | number | No | Sampling temperature (0-2). If omitted, NanoGPT does not force a value and the routed provider/model default applies (OpenAI defaults to 1.0). Not supported by reasoning-capable models | | `top_p` | number | No | Nucleus sampling parameter. Not supported by reasoning-capable models | | `presence_penalty` | number | No | Presence penalty for sampling (-2.0 to 2.0) | | `frequency_penalty` | number | No | Frequency penalty for sampling (-2.0 to 2.0) | | `top_logprobs` | integer | No | Number of top logprobs to return (0-20) | | `tools` | array | No | Array of tools available to the model. Compatible models can use [hosted tool search](/api-reference/miscellaneous/hosted-tool-search) with deferred function definitions. | | `tool_choice` | string or object | No | Tool use: `auto`, `none`, `required`, `{ type: "function", name: "..." }`, or `{ type: "allowed_tools", ... }` | | `parallel_tool_calls` | boolean | No | Allow multiple tool calls in parallel | | `stream` | boolean | No | Enable streaming responses (default: false) | | `stream_options` | object | No | Streaming options: `{ include_obfuscation?: boolean }` | | `store` | boolean | No | Store the response locally for later retrieval/threading/background processing. Set `false` to disable stored Responses API data for the request. | | `retention_days` | integer or null | No | Per-request retention override in days (`0..365`). `null` means no request-level override | | `retentionDays` | integer or null | No | Alias for `retention_days`. If both are sent, values must match | | `previous_response_id` | string | No | Link to previous response for conversation threading | | `reasoning` | object | No | Reasoning configuration. Setting `reasoning.effort` to any non-`none` value explicitly requests reasoning mode. | | `text` | object | No | Text output configuration (format + verbosity) | | `metadata` | object | No | Custom metadata (max 16 keys, 64 char keys, 512 char values) | | `truncation` | string | No | Truncation strategy: `auto` or `disabled` | | `user` | string | No | Unique user identifier | | `seed` | integer | No | Optional integer forwarded on model/provider routes that support seeded sampling. This may improve reproducibility but does not guarantee identical output. Results can change if NanoGPT selects a different automatic or fallback route, or if the provider changes its backend. | | `conversation` | object | No | Conversation context: `{ id?: string, messages?: InputItem[] }` | | `include` | string\[] | No | Additional fields to include in response | | `safety_identifier` | string | No | Safety tracking identifier | | `prompt_cache_key` | string | No | Key for prompt caching | | `background` | boolean | No | Enable background/async processing | | `service_tier` | string | No | Service tier: `"auto"`, `"default"`, `"flex"`, or `"priority"`. See [Service tiers (flex and priority)](#service-tiers-priority) near the end. | ### Reproducibility guidance Seeded generation is best-effort. To reduce avoidable variation: * Keep the exact model, input, instructions, tools, and sampling settings unchanged. * Select a specific provider where possible. * Disable automatic fallbacks where supported when route consistency matters. * Use a low or zero `temperature` where supported. * Do not treat seeded output as byte-identical. Record provider, route, and system-fingerprint metadata when NanoGPT exposes reliable values. NanoGPT will publish a seed-support matrix when reliable route-level capability data is available. Until then, do not infer seed support from endpoint compatibility alone. ### Response Storage And Retention NanoGPT supports local Responses API storage for features that need server-side state, including response retrieval, `previous_response_id` threading, and background processing. Set `store: false` to disable stored Responses API data for a request: ```json theme={null} { "model": "gpt-5.2", "input": "Hello", "store": false } ``` When response storage is enabled and no override applies, stored Responses API records are retained for up to 7 days. You can override retention per request with either `retentionDays` or `retention_days`: ```json theme={null} { "model": "gpt-5.2", "input": "Hello", "store": true, "retentionDays": 3 } ``` or: ```json theme={null} { "model": "gpt-5.2", "input": "Hello", "store": true, "retention_days": 3 } ``` A retention value of `0` means do not retain stored response data for that request: ```json theme={null} { "model": "gpt-5.2", "input": "Hello", "store": true, "retentionDays": 0 } ``` Valid per-request retention values are integers from `0` to `365` days, or `null` to use the next configured default. If both `retentionDays` and `retention_days` are sent, they must match. Team and user defaults are configurable through API endpoints, not the main web Settings page today. Team owners/admins can set `responses_retention_days` with `PATCH /api/teams/{teamUuid}/settings`; users can set `responsesRetentionDays` with `POST /api/user/responses-retention`. See [Teams: response retention defaults](/api-reference/teams#user-response-retention-default). ### Retention Resolution Effective retention for `/v1/responses` resolves in this order: 1. Request override (`retention_days` / `retentionDays`) 2. Team setting (`responses_retention_days`) 3. User setting (`responsesRetentionDays`) 4. Platform default (`7` days) Rules: * `retention_days` and `retentionDays` accept integer values `0..365`, or `null`. * `null` means "no request override" and falls back to team/user/platform defaults. * If both request fields are provided, they must match. * Invalid retention values return `400` with `invalid_request_error`. * `0` enables zero-retention behavior for that request. * Existing clients that omit retention fields keep default behavior (team/user/platform retention resolution). With effective retention `0`: * `previous_response_id` is rejected. * `background` is rejected. API-key team context for retention defaults: * If `x-team-id` is present and the caller is a member, that team is used. * Otherwise, the API uses the caller session's default team (`default_team_uuid` / `default_team_id`) when membership is valid. ### Input Types The `input` parameter accepts either a simple string or an array of input items. #### Simple String Input ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": "What is the capital of France?" } ``` #### Array Input ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": [ { "type": "message", "role": "user", "content": "What is the capital of France?" } ] } ``` ### Input Item Types | Type | Description | | ---------------------- | -------------------------------------- | | `message` | A message with role and content | | `function_call` | A tool/function call made by the model | | `function_call_output` | The result of a tool/function call | #### Message Item ```json theme={null} { "type": "message", "role": "user", "content": "Hello, how are you?" } ``` Supported roles: `user`, `assistant`, `system`, `developer` Content can be a string or an array of content parts: ```json theme={null} { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "What's in this image?" }, { "type": "input_image", "image_url": "https://example.com/image.jpg" } ] } ``` ### Content Part Types | Type | Description | | ------------- | --------------------------------------------- | | `input_text` | Text input | | `input_image` | Image input (via URL or file\_id) | | `input_video` | Video input (via HTTPS URL or video data URL) | | `input_file` | File input | | `output_text` | Text output (includes annotations/logprobs) | | `refusal` | Model refusal | #### Image Input ```json theme={null} { "type": "input_image", "image_url": "https://example.com/image.jpg", "detail": "auto" } ``` The `detail` parameter can be: `auto`, `low`, or `high`. #### Video Input Use `input_video` for video understanding on a model that advertises video input: ```json theme={null} { "model": "google/gemini-3.1-flash-lite", "input": [{ "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "Summarize the action." }, { "type": "input_video", "video_url": "data:video/mp4;base64,AAAA..." } ] }] } ``` `video_url` may be a public HTTPS URL or a valid `data:video/*;base64,...` URL. Chat-style `video_url` content parts are accepted as a compatibility alias, but `input_video` is the canonical Responses shape. `input_file` is treated as video only when NanoGPT can safely identify it from a `video/*` MIME type, video data URL, or recognized video filename/URL extension. PDFs, audio files, and unknown or opaque files are not silently treated as video. An opaque `file_id` is not resolved for Responses video input; it returns `video_file_id_not_supported`. Video is currently rejected in Responses Batch. Segment offsets are validated but no current public text-model route can honor them; valid offsets return `video_segment_not_supported`. #### Function Call Item ```json theme={null} { "type": "function_call", "id": "fc_123", "call_id": "call_abc123", "name": "get_weather", "arguments": "{\"location\": \"Paris\"}" } ``` #### Function Call Output Item ```json theme={null} { "type": "function_call_output", "call_id": "call_abc123", "output": "{\"temperature\": 22, \"condition\": \"sunny\"}" } ``` ## Tools Provide function tools and built-in tools the model can use: ### Function Tool Define functions that the model can call: ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": "What's the weather in Paris?", "tools": [ { "type": "function", "name": "get_weather", "description": "Get current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "City name" } }, "required": ["location"] }, "strict": false } ], "tool_choice": "auto" } ``` ### Hosted Tool Search For large function catalogs, add one `nanogpt:tool_search` entry (or its `tool_search` alias) and mark discoverable functions with `defer_loading: true`: ```json theme={null} { "model": "openai/gpt-5.5", "input": "Find the weather in Amsterdam", "tools": [ { "type": "nanogpt:tool_search", "max_results": 5 }, { "type": "function", "name": "weather_forecast", "description": "Get the weather forecast for a city", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] }, "defer_loading": true } ], "tool_choice": "auto" } ``` The search activity is returned as `tool_search_call`. A selected function is still returned as a normal `function_call`; your client executes it and sends `function_call_output`. See [Hosted tool search](/api-reference/miscellaneous/hosted-tool-search) for limits, billing, authorization, and compatibility. ### Web Search Tool ```json theme={null} { "type": "web_search_preview", "search_context_size": "low", "user_location": { "type": "approximate", "country": "US", "city": "San Francisco", "region": "California" } } ``` ### File Search Tool ```json theme={null} { "type": "file_search", "vector_store_ids": ["vs_..."], "max_num_results": 10, "ranking_options": { "ranker": "auto", "score_threshold": 0.5 } } ``` ### Code Interpreter Tool ```json theme={null} { "type": "code_interpreter", "container": { "type": "auto" } } ``` ### MCP Tool ```json theme={null} { "type": "mcp", "server_label": "my-server", "server_url": "https://...", "headers": { "Authorization": "Bearer ..." }, "require_approval": "auto" } ``` ### Image Generation Tool ```json theme={null} { "type": "image_generation" } ``` ### Tool Choice Use `allowed_tools` to restrict which tools the model may choose from: ```json theme={null} { "tool_choice": { "type": "allowed_tools", "tools": [{ "type": "function", "name": "get_weather" }], "mode": "auto" } } ``` ### Function Tool Normalization Function tools in responses always include nullable fields: ```json theme={null} { "type": "function", "name": "get_weather", "description": null, "parameters": null, "strict": null } ``` ## Reasoning Configuration Use `reasoning` to control depth and visibility of reasoning output: ```json theme={null} { "model": "anthropic/claude-opus-4.5", "input": "Solve this complex problem...", "reasoning": { "effort": "high", "summary": "auto" } } ``` | Parameter | Values | Description | | --------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `effort` | `none`, `minimal`, `low`, `medium`, `high`, `xhigh` | Reasoning depth. Any value other than `none` explicitly requests reasoning mode. | | `summary` | `none`, `auto`, `detailed`, `concise` | Reasoning summary format | | `exclude` | `true`, `false` | Controls output visibility (hides reasoning fields/blocks). It does not inherently disable reasoning compute. | ## Text/Format Configuration Control response format and verbosity: ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": "List 3 colors", "text": { "format": { "type": "json_object" }, "verbosity": "medium" } } ``` ### Text Parameter Structure ```json theme={null} { "format": { "type": "text" } | { "type": "json_object" } | { "type": "json_schema", "json_schema": { ... } }, "verbosity": "low" | "medium" | "high" } ``` ### Format Types * `{ "type": "text" }` - Plain text (default) * `{ "type": "json_object" }` - JSON object output * `{ "type": "json_schema", "json_schema": { ... } }` - Structured JSON with schema ### Verbosity Values * `low` - Short, compact responses * `medium` - Balanced detail * `high` - Most detailed output ### JSON Schema Format ```json theme={null} { "text": { "format": { "type": "json_schema", "json_schema": { "name": "color_list", "schema": { "type": "object", "properties": { "colors": { "type": "array", "items": { "type": "string" } } } }, "strict": true } } } } ``` ## Response Format ### Successful Response ```json theme={null} { "id": "resp_abc123", "object": "response", "created_at": 1699000000, "completed_at": 1699000001, "model": "openai/gpt-5.6-sol", "status": "completed", "instructions": null, "previous_response_id": null, "tools": [], "tool_choice": "auto", "parallel_tool_calls": false, "truncation": "disabled", "text": { "format": { "type": "text" }, "verbosity": "medium" }, "reasoning": null, "temperature": 1, "top_p": 1, "presence_penalty": 0, "frequency_penalty": 0, "top_logprobs": 0, "max_output_tokens": null, "max_tool_calls": null, "user": null, "store": true, "background": false, "safety_identifier": null, "prompt_cache_key": null, "output": [ { "type": "message", "id": "msg_xyz789", "role": "assistant", "status": "completed", "content": [ { "type": "output_text", "text": "The capital of France is Paris.", "annotations": [], "logprobs": [] } ] } ], "output_text": "The capital of France is Paris.", "usage": { "input_tokens": 15, "output_tokens": 10, "total_tokens": 25, "input_tokens_details": { "cached_tokens": 0 }, "output_tokens_details": { "reasoning_tokens": 0 } }, "metadata": {}, "service_tier": "auto" } ``` ### Response Fields All fields below are always present; nullable values indicate an option was not set. | Field | Type | Description | | ---------------------- | ---------------- | ------------------------------------------------- | | `id` | string | Unique response identifier (format: `resp_*`) | | `object` | string | Always `"response"` | | `created_at` | integer | Unix timestamp of creation | | `completed_at` | integer or null | Unix timestamp when response completed | | `model` | string | Model used for the response | | `status` | string | Response status | | `instructions` | string or null | System instructions used | | `previous_response_id` | string or null | ID of previous response in conversation | | `tools` | array | Tools available (normalized with nullable fields) | | `tool_choice` | string or object | Tool choice setting used | | `parallel_tool_calls` | boolean | Whether parallel tool calls were enabled | | `truncation` | string | Truncation strategy: `auto` or `disabled` | | `text` | object | Resolved text configuration | | `reasoning` | object or null | Reasoning configuration | | `temperature` | number | Temperature used | | `top_p` | number | Top-p value used | | `presence_penalty` | number | Presence penalty used | | `frequency_penalty` | number | Frequency penalty used | | `top_logprobs` | number | Top logprobs setting | | `max_output_tokens` | integer or null | Max output tokens setting | | `max_tool_calls` | integer or null | Max tool calls setting | | `user` | string or null | User identifier | | `store` | boolean | Whether response was stored | | `background` | boolean | Whether processed in background | | `safety_identifier` | string or null | Safety identifier | | `prompt_cache_key` | string or null | Prompt cache key | | `output` | array | Array of output items | | `output_text` | string | Convenience field with concatenated text output | | `usage` | object | Token usage statistics | | `error` | object | Error details (if status is `failed`) | | `incomplete_details` | object | Details if status is `incomplete` | | `metadata` | object | Custom metadata (if provided) | | `service_tier` | string | Service tier used (echoed when provided) | ### Usage Object The `usage` object always includes token details: ```json theme={null} { "input_tokens": 100, "output_tokens": 50, "total_tokens": 150, "input_tokens_details": { "cached_tokens": 0 }, "output_tokens_details": { "reasoning_tokens": 0 } } ``` ### Response Status Values | Status | Description | | ------------- | ------------------------------ | | `queued` | Background request is queued | | `in_progress` | Request is being processed | | `completed` | Request completed successfully | | `incomplete` | Response was truncated | | `failed` | Request failed with error | | `cancelled` | Request was cancelled | ### `reasoning` Response Field ```json theme={null} { "effort": "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | null, "summary": "none" | "auto" | "detailed" | "concise" | null } ``` ### `text` Response Field (Resolved) ```json theme={null} { "format": { "type": "text" | "json_object" | "json_schema", "...": "..." }, "verbosity": "low" | "medium" | "high" | undefined } ``` ### Output Item Types All output items include a `status` field. #### Message Output ```json theme={null} { "type": "message", "id": "msg_123", "role": "assistant", "status": "completed", "content": [ { "type": "output_text", "text": "Response text here", "annotations": [], "logprobs": [] } ] } ``` #### Function Call Output ```json theme={null} { "type": "function_call", "id": "fc_123", "call_id": "call_abc", "name": "get_weather", "arguments": "{\"location\": \"Paris\"}", "status": "completed" } ``` #### Hosted Tool Search Call ```json theme={null} { "type": "tool_search_call", "id": "ts_123", "status": "completed" } ``` This item records discovery activity. It does not execute or approve a client function. A revealed function selected by the model appears separately as `function_call`. #### Reasoning Output (reasoning-capable models) ```json theme={null} { "type": "reasoning", "id": "reasoning_123", "status": "completed", "summary": [ { "type": "summary_text", "text": "I analyzed the problem by..." } ], "content": [ { "type": "reasoning_text", "text": "Detailed reasoning goes here." } ], "encrypted_content": null } ``` #### Web Search Call Output ```json theme={null} { "type": "web_search_call", "id": "ws_123", "status": "completed", "action": { "query": "search query" }, "results": [{ "url": "...", "title": "...", "snippet": "..." }] } ``` #### Image Generation Call Output ```json theme={null} { "type": "image_generation_call", "id": "ig_123", "status": "completed", "result": { "b64_json": "...", "url": "...", "revised_prompt": "..." } } ``` #### Computer Call Output ```json theme={null} { "type": "computer_call", "id": "cc_123", "call_id": "call_abc123", "status": "completed", "action": { "type": "click" }, "pending_safety_checks": [{ "id": "...", "code": "...", "message": "..." }] } ``` ### Output Item Status Values | Status | Description | | ------------- | ------------------------------ | | `completed` | Item finished successfully | | `in_progress` | Item still being generated | | `incomplete` | Item was truncated/interrupted | ### Output Text Parts Output text parts include annotations and logprobs: ```json theme={null} { "type": "output_text", "text": "Hello world", "annotations": [], "logprobs": [ { "token": "Hello", "logprob": -0.5, "bytes": [72, 101, 108, 108, 111], "top_logprobs": [ { "token": "Hello", "logprob": -0.5, "bytes": [72, 101, 108, 108, 111] }, { "token": "Hi", "logprob": -1.2, "bytes": [72, 105] } ] } ] } ``` ### Annotation Types #### URL Citation ```json theme={null} { "type": "url_citation", "start_index": 0, "end_index": 10, "url": "https://...", "title": "Page Title" } ``` #### File Citation ```json theme={null} { "type": "file_citation", "start_index": 0, "end_index": 10, "file_id": "file_..." } ``` #### File Path ```json theme={null} { "type": "file_path", "start_index": 0, "end_index": 10, "file_id": "file_..." } ``` ## Streaming See also: [Streaming Protocol (SSE)](/api-reference/miscellaneous/streaming-protocol). Enable streaming to receive incremental response updates: ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": "Write a short story", "stream": true } ``` ### Streaming Response The response is delivered as Server-Sent Events (SSE): ``` data: {"type":"response.created","response":{...},"sequence_number":0} data: {"type":"response.in_progress","response":{...},"sequence_number":1} data: {"type":"response.output_item.added","output_index":0,"item":{...},"sequence_number":2} data: {"type":"response.output_text.delta","item_id":"msg_...","output_index":0,"content_index":0,"delta":"The ","logprobs":[...],"sequence_number":3} data: {"type":"response.output_text.delta","item_id":"msg_...","output_index":0,"content_index":0,"delta":"capital ","logprobs":[...],"sequence_number":4} data: {"type":"response.output_text.done","item_id":"msg_...","output_index":0,"content_index":0,"text":"The capital of France is Paris.","logprobs":[...],"sequence_number":10} data: {"type":"response.completed","response":{...},"sequence_number":11} data: [DONE] ``` ### Streaming Event Types | Event | Description | | ---------------------------------------- | ------------------------------- | | `response.created` | Response object created | | `response.in_progress` | Processing started | | `response.output_item.added` | New output item started | | `response.output_item.done` | Output item completed | | `response.content_part.added` | Content part started | | `response.content_part.done` | Content part completed | | `response.output_text.delta` | Incremental text chunk | | `response.output_text.done` | Text content completed | | `response.reasoning.delta` | Incremental reasoning text | | `response.reasoning.done` | Reasoning content completed | | `response.function_call_arguments.delta` | Incremental function arguments | | `response.function_call_arguments.done` | Function call completed | | `response.completed` | Response completed successfully | | `response.incomplete` | Response truncated | | `response.failed` | Response failed | ### Updated Event Fields * All content/output events include `item_id` for the parent output item. * Text delta/done events include `logprobs`. Example `response.output_text.delta`: ```json theme={null} { "type": "response.output_text.delta", "item_id": "msg_...", "output_index": 0, "content_index": 0, "delta": "Hello", "logprobs": [...], "sequence_number": 5 } ``` ## Conversation Threading Chain responses together for multi-turn conversations. You can use `previous_response_id` or the `conversation` object (`id` or `messages`) to manage context. ### First Request ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": "My name is Alice." } ``` Response includes `id`: `"resp_abc123"` ### Follow-up Request ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": "What is my name?", "previous_response_id": "resp_abc123" } ``` The model has access to the conversation history and responds: "Your name is Alice." Note: `previous_response_id` requires authentication, `store: true` on previous responses, and effective retention greater than `0`. ## Background Mode For long-running requests, use background mode to receive an immediate response and poll for results. ### Initiate Background Request ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": "Write a detailed analysis...", "background": true } ``` ### Immediate Response (202 Accepted) ```json theme={null} { "id": "resp_abc123", "object": "response", "created_at": 1699000000, "model": "openai/gpt-5.6-sol", "status": "queued", "output": [] } ``` ### Poll for Completion ```http theme={null} GET /v1/responses/resp_abc123 Authorization: Bearer YOUR_API_KEY ``` Keep polling until `status` is `completed`, `failed`, or `incomplete`. Constraints: * Cannot be combined with `stream: true` * Requires authentication * Effective retention must be greater than `0` * Maximum processing time: approximately 800 seconds ## Retrieve Response ```http theme={null} GET /v1/responses/{id} Authorization: Bearer YOUR_API_KEY ``` ### Response Returns the full response object (same format as POST response). ### Errors * `404` - Response not found or belongs to different account * `401` - Authentication required/invalid ## Delete Response ```http theme={null} DELETE /v1/responses/{id} Authorization: Bearer YOUR_API_KEY ``` ### Response ```json theme={null} { "id": "resp_abc123", "object": "response.deleted", "deleted": true } ``` ## Error Handling ### Error Response Format ```json theme={null} { "error": { "code": "missing_required_parameter", "message": "model is required" } } ``` ### HTTP Status Codes | HTTP Status | Description | | ----------- | -------------------------- | | `400` | Invalid request parameters | | `401` | Missing or invalid API key | | `403` | Insufficient permissions | | `404` | Resource not found | | `429` | Rate limit exceeded | | `500` | Internal server error | | `503` | Service unavailable | ### Common Error Codes | Code | Description | | ---------------------------- | ------------------------------------------------------------------------------------------- | | `missing_required_parameter` | Required parameter not provided | | `model_not_found` | Specified model does not exist | | `response_not_found` | Response ID not found | | `invalid_response_id` | Invalid response ID format | | `invalid_request_error` | Invalid request shape/value (for example retention out of range or mismatched alias fields) | | `authentication_required` | No API key provided | | `invalid_api_key` | API key is invalid or inactive | ## Complete Examples ### Simple Text Completion ```bash theme={null} curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Explain quantum computing in one sentence." }' ``` ### Multi-turn Conversation ```bash theme={null} # First turn curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "I want to learn Python programming." }' # Second turn (using response ID from first request) curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Where should I start?", "previous_response_id": "resp_abc123" }' ``` ### Streaming Response ```bash theme={null} curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Write a haiku about programming", "stream": true }' ``` ### Per-request Retention Override ```bash theme={null} curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "gpt-4o", "input": "hello", "store": true, "retention_days": 3 }' ``` ### Function Calling ```bash theme={null} curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "What is the weather in Tokyo?", "tools": [ { "type": "function", "name": "get_weather", "description": "Get current weather for a location", "parameters": { "type": "object", "properties": { "location": { "type": "string" } }, "required": ["location"] } } ] }' ``` ### Submitting Tool Results ```bash theme={null} curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": [ { "type": "message", "role": "user", "content": "What is the weather in Tokyo?" }, { "type": "function_call", "id": "fc_1", "call_id": "call_123", "name": "get_weather", "arguments": "{\"location\": \"Tokyo\"}" }, { "type": "function_call_output", "call_id": "call_123", "output": "{\"temperature\": 18, \"condition\": \"cloudy\"}" } ] }' ``` ### Image Input (Vision) ```bash theme={null} curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": [ { "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "What is in this image?" }, { "type": "input_image", "image_url": "https://example.com/photo.jpg", "detail": "auto" } ] } ] }' ``` ### Video Input (Understanding) Video input is separate from video generation and is available only on models advertising video capability. See the [Video Input guide](/api-reference/miscellaneous/video-input) for source rules, limits, YouTube behavior, and validation errors. ### JSON Output ```bash theme={null} curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "List the planets in our solar system", "text": { "format": { "type": "json_object" } } }' ``` ### Background Processing ```bash theme={null} # Start background request curl -X POST https://api.nano-gpt.com/api/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{ "model": "openai/gpt-5.6-sol", "input": "Generate a comprehensive report...", "background": true }' # Poll for results curl https://api.nano-gpt.com/api/v1/responses/resp_abc123 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## Limitations 1. Deep research models: Deep research variants are not supported. 2. GPU-TEE streaming: Streaming is not supported for GPU-TEE models. Use `/v1/chat/completions` for these models. 3. Background mode: Maximum duration is approximately 800 seconds. 4. Metadata limits: Maximum 16 keys, 64 character key names, 512 character values. 5. Hosted tool search: Available only on compatible models in foreground Responses requests. It cannot currently be combined with background mode or other hosted built-ins. See [Hosted tool search](/api-reference/miscellaneous/hosted-tool-search). ## Service tiers (flex and priority) Set `service_tier` to request a non-default capacity tier on providers that support service tiers: * `auto` or omitted: use NanoGPT's normal routing and the provider default. * `default`: request the provider's standard tier where the provider accepts an explicit default value. * `flex`: request lower-cost, variable-capacity processing where supported. * `priority`: request higher-cost priority processing where supported. Behavior notes: * Service tier availability is model- and provider-specific. Model pages show which tiers are supported. * Flex and priority tiers are only applied when the routed provider supports them. * Header provider overrides (like `X-Provider`) and explicit provider selection are honored for pricing and x402 estimates. * Provider-native web search can force routing; tier pricing follows that routing. * If you explicitly force a provider that does not support service tiers, the requested tier may be ignored by the upstream provider, or routing and pricing may differ from the default route. Billing note: * Flex tier billing uses flex pricing where applicable. * Priority tier billing uses priority pricing where applicable. * High-context pricing may also apply for models and providers with separate high-context SKUs, such as `es2k` pricing for GPT-5.5/GPT-5.4 where available. ### Example: flex tier ```json theme={null} { "model": "openai/gpt-5.5", "input": "Say hi in one sentence.", "service_tier": "flex" } ``` ### Example: priority tier ```json theme={null} { "model": "openai/gpt-5.5", "input": "Say hi in one sentence.", "service_tier": "priority" } ``` ## Response Headers All responses include: | Header | Description | | -------------- | ----------------------------------------- | | `X-Request-ID` | Unique request/response identifier | | `Content-Type` | `application/json` or `text/event-stream` | # Web Scraping Source: https://docs.nano-gpt.com/api-reference/endpoint/scrape-urls POST /scrape-urls Extract clean, formatted content from web pages. Returns both raw HTML content and formatted markdown. ## Overview The NanoGPT Web Scraping API allows you to extract clean, formatted content from web pages. It uses the Firecrawl service to scrape URLs and returns both raw HTML content and formatted markdown. You can also call this tool through the unified [Data API](/api-reference/endpoint/data-api) at `POST /api/v1/data/url/scrape`. The Data API preserves this endpoint's request body, response body, billing, and validation behavior while adding discovery and dispatch metadata. ## Authentication The API supports two authentication methods: ### 1. API Key Authentication (Recommended) Include your API key in the request header: ``` x-api-key: YOUR_API_KEY ``` ### 2. Bearer Token Authentication ``` Authorization: Bearer YOUR_API_KEY ``` ## Accountless x402 Payment For accountless payment, prefer the public Data API path: ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/url/scrape \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "urls": ["https://nano-gpt.com"], "stealthMode": false }' ``` To request an accountless x402 quote, send the API request without `Authorization` or `x-api-key`, and include `x-x402: true`. NanoGPT will return `402 Payment Required` with available payment options. This endpoint supports accountless x402 payments where listed by `GET /api/v1/x402/endpoints`, including Lightning L402 when advertised. See [Accountless x402 API Payments](/api-reference/miscellaneous/x402) for the full flow. If you receive `401 missing_api_key` immediately, check that the initial quote request includes `x-x402: true`. Without that header, NanoGPT does not enter the x402 quote flow. ## Request Format ### Headers ``` Content-Type: application/json x-api-key: YOUR_API_KEY ``` ### Request Body ```json theme={null} { "urls": [ "https://example.com/article", "https://blog.com/post", "https://news.site.com/story" ], "stealthMode": false } ``` ### Parameters | Parameter | Type | Required | Description | | ----------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- | | urls | string\[] | Yes | Array of URLs to scrape. Maximum 5 URLs per request. | | stealthMode | boolean | No | Optional. Default `false`. When `true`, multiplies the upfront per-URL charge by 5 and routes requests through the stealth proxy. | #### Stealth scraping (optional) Set `stealthMode: true` to run requests through Firecrawl’s stealth proxy for tougher targets. Stealth scraping costs 5× the standard per-URL rate and still counts toward the configured URL cap. The web UI exposes the same toggle, so use this field to mirror that behavior from the API. ```jsonc theme={null} POST /api/scrape-urls { "urls": ["https://example.com/restricted"], "stealthMode": true } ``` The response `summary` includes `stealthModeUsed` so you can track when the surcharge applied. ### URL Requirements * Must be valid HTTP or HTTPS URLs * Must have standard web ports (80, 443, or default) * Cannot be localhost, private IPs, or metadata endpoints * YouTube URLs are not supported (use the YouTube transcription endpoint instead) ## Response Format ### Success Response (200 OK) ```json theme={null} { "results": [ { "url": "https://example.com/article", "success": true, "title": "Article Title", "content": "Raw HTML content...", "markdown": "# Article Title\n\nFormatted markdown content..." }, { "url": "https://invalid.site.com", "success": false, "error": "Failed to scrape URL" } ], "summary": { "requested": 3, "processed": 3, "successful": 2, "failed": 1, "totalCost": 0.002, "stealthModeUsed": false } } ``` ### Response Fields #### results Array of scraping results for each URL: * `url` (string): The URL that was scraped * `success` (boolean): Whether the scraping was successful * `title` (string, optional): Page title if successfully scraped * `content` (string, optional): Raw HTML content * `markdown` (string, optional): Formatted markdown version of the content * `error` (string, optional): Error message if scraping failed #### summary Summary statistics for the request: * `requested` (number): Number of URLs in the original request * `processed` (number): Number of valid URLs that were processed * `successful` (number): Number of URLs successfully scraped * `failed` (number): Number of URLs that failed to scrape * `totalCost` (number): Total cost in USD (only for successful scrapes) * `stealthModeUsed` (boolean): Indicates whether stealth mode was enabled for any processed URLs ## Error Responses ### 400 Bad Request ```json theme={null} { "error": "Please provide an array of URLs to scrape" } ``` ### 401 Unauthorized ```json theme={null} { "error": "Invalid session" } ``` ### 402 Payment Required ```json theme={null} { "error": "Insufficient balance" } ``` ### 429 Too Many Requests ```json theme={null} { "error": "Rate limit exceeded. Please wait before sending another request." } ``` ### 500 Internal Server Error ```json theme={null} { "error": "An error occurred while processing your request" } ``` ## Pricing * **Cost**: \$0.001 per successfully scraped URL * **Billing**: You are only charged for URLs that are successfully scraped * **Payment Methods**: USD balance or Nano (XNO) cryptocurrency * **Note (inline chat scraping)**: Inline URL scraping inside `POST /api/v1/chat/completions` (via `scraping: true`) is billed separately at **\$0.0015 per successfully scraped URL**. Use this endpoint when you want explicit URL lists and the lower standalone price. ## Rate Limits * **Default**: 30 requests per minute per IP address * **With API Key**: 30 requests per minute per API key ## Code Examples ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/scrape-urls \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "urls": [ "https://example.com/article", "https://blog.com/post" ] }' ``` ```python Python theme={null} import requests api_key = "YOUR_API_KEY" urls = [ "https://example.com/article", "https://blog.com/post" ] response = requests.post( "https://nano-gpt.com/api/scrape-urls", headers={ "Content-Type": "application/json", "x-api-key": api_key }, json={"urls": urls} ) data = response.json() for result in data["results"]: if result["success"]: print(f"Title: {result['title']}") print(f"Markdown: {result['markdown'][:200]}...") else: print(f"Failed to scrape {result['url']}: {result['error']}") ``` ```javascript JavaScript/TypeScript theme={null} const apiKey = 'YOUR_API_KEY'; const urls = [ 'https://example.com/article', 'https://blog.com/post' ]; const response = await fetch('https://nano-gpt.com/api/scrape-urls', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey }, body: JSON.stringify({ urls }) }); const data = await response.json(); data.results.forEach(result => { if (result.success) { console.log(`Title: ${result.title}`); console.log(`Markdown: ${result.markdown.substring(0, 200)}...`); } else { console.log(`Failed to scrape ${result.url}: ${result.error}`); } }); ``` ## Best Practices 1. **Batch Requests**: Send multiple URLs in a single request (up to 5) to minimize API calls 2. **Error Handling**: Always check the `success` field for each result before accessing content 3. **Content Size**: Scraped content is limited to 100KB per URL 4. **URL Validation**: Validate URLs on your end before sending to reduce failed requests 5. **Markdown Format**: Use the markdown field for better readability and formatting ## Limitations * Maximum 5 URLs per request * Maximum content size: 100KB per URL * No JavaScript rendering (static content only) ## FAQ **Q: Why was my URL rejected?** A: URLs can be rejected for several reasons: * Invalid format (not HTTP/HTTPS) * Pointing to localhost or private IPs * Using non-standard ports * Being a YouTube URL (use the YouTube transcription endpoint) **Q: Can I scrape JavaScript-heavy sites?** A: The scraper fetches static HTML content. Sites that rely heavily on JavaScript may not return complete content. **Q: What happens if a URL fails to scrape?** A: You are not charged for failed URLs. The response will include an error message for that specific URL. **Q: Is there a sandbox/test environment?** A: You can test with your regular API key. Since you're only charged for successful scrapes, failed attempts during testing won't cost anything. # v1/audio/speech (TTS + Music) Source: https://docs.nano-gpt.com/api-reference/endpoint/speech POST https://nano-gpt.com/api/v1/audio/speech ## Overview Synthesize speech (TTS) or generate music with a single request. The OpenAI-compatible `POST /v1/audio/speech` endpoint returns audio bytes directly in the HTTP response. Optional chunked streaming is available with `stream: true`. In streaming mode, audio bytes are delivered progressively as generated, which reduces time-to-first-byte (TTFB) for real-time playback. Default behavior is unchanged: omit `stream` (or set `false`) to receive one buffered audio file after generation completes. When you use a music model (for example `Minimax-Music-02`), the `input` field is treated as a music prompt (not text to speak) and `voice` is ignored. For a dedicated music guide and model list, see `api-reference/music-generation.mdx`. ## Endpoint * Method/Path: `POST https://nano-gpt.com/api/v1/audio/speech` * Auth: `Authorization: Bearer ` * Required header: `Content-Type: application/json` * You may see older examples using `POST https://nano-gpt.com/api/v1/speech`. Prefer `/api/v1/audio/speech` for OpenAI SDK compatibility. ## Request Parameters | Parameter | Type | Default | Description | | ----------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- | | `model` | string | None | Audio-speech model ID (TTS or music). | | `input` | string | None | Text to speak (TTS) or a music prompt (music models). | | `voice` | string | None | Voice preset for TTS. Ignored for music models. If your client requires this field, pass any string (for example `"alloy"`). | | `response_format` | string | `mp3` | Output format when supported by the selected provider/model. | | `speed` | number | `1` | Speaking rate multiplier for TTS (for example `0.5`-`2.0`). Ignored for music models. | | `instructions` | string | None | Voice/style instructions (supported by some models/providers). | | `stream` | boolean | `false` | When `true`, returns chunked audio bytes progressively. | Notes: * Some provider-backed models may support additional fields; accepted parameters vary by model. * For unsupported models, `stream: true` is ignored and the endpoint returns the normal buffered response. ## Streaming Support (TTS Models) | Model | Provider | Streaming Support | | ----------------------- | -------------------- | ----------------- | | `tts-1` | OpenAI | Yes | | `tts-1-hd` | OpenAI | Yes | | `gpt-4o-mini-tts` | OpenAI | Yes | | `Elevenlabs-Turbo-V2.5` | ElevenLabs (via FAL) | Yes | | `Elevenlabs-V3` | ElevenLabs (via FAL) | Yes | All other TTS models (Gemini, Inworld, Kokoro, Qwen, MiniMax, and others) ignore `stream` and return buffered responses. ## Response Behavior ### Non-streaming (default) * Status: `200 OK` * Body: complete audio file returned once generation finishes * Headers: typically includes `Content-Length` ### Streaming (`stream: true`) * Status: `200 OK` * Body: audio bytes arrive progressively in chunks * Headers: no `Content-Length`; uses `Transfer-Encoding: chunked` * Client can start playback/processing as soon as the first chunk arrives * `Content-Type` by provider: * OpenAI TTS models: matches the selected output format (for example `audio/mpeg`, `audio/wav`, `audio/opus`, `audio/flac`, `audio/aac`, `audio/pcm`) * ElevenLabs TTS models: always `audio/mpeg` ### Errors * If an error happens before streaming starts, the API returns the standard OpenAI-style JSON error envelope: ```json theme={null} { "error": { "message": "...", "type": "invalid_request_error", "param": null, "code": "invalid_request" } } ``` * If streaming fails after bytes have started, the connection is terminated and clients may receive partial/corrupt audio. Common error types: `invalid_model`, `invalid_voice`, `unsupported_format`, `input_too_long`, `rate_limit_exceeded`. ## Examples ### Non-streaming request (unchanged) ```bash theme={null} curl -X POST \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ https://nano-gpt.com/api/v1/audio/speech \ -d '{ "model": "gpt-4o-mini-tts", "input": "Hello from NanoGPT!", "voice": "alloy", "response_format": "mp3" }' \ --output speech.mp3 ``` ### Streaming request (cURL) ```bash theme={null} curl -X POST \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ https://nano-gpt.com/api/v1/audio/speech \ -d '{ "model": "gpt-4o-mini-tts", "input": "Hello, this is a streaming test with a longer message to observe progressive chunking.", "voice": "alloy", "response_format": "mp3", "stream": true }' \ --output speech.mp3 ``` ### OpenAI Node.js SDK (streaming) ```ts theme={null} import OpenAI from "openai"; import { createWriteStream } from "node:fs"; const client = new OpenAI({ apiKey: process.env.NANOGPT_API_KEY, baseURL: "https://nano-gpt.com/api/v1", }); const response = await client.audio.speech.create({ model: "gpt-4o-mini-tts", voice: "alloy", input: "Hello from the streaming API!", response_format: "mp3", stream: true, }); if (!response.body) throw new Error("Missing response body stream"); const out = createWriteStream("output.mp3"); const reader = response.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; out.write(Buffer.from(value)); } out.end(); ``` ### OpenAI Python SDK (streaming) ```python theme={null} from openai import OpenAI client = OpenAI( api_key="your-nanogpt-api-key", base_url="https://nano-gpt.com/api/v1", ) with client.audio.speech.with_streaming_response.create( model="gpt-4o-mini-tts", voice="alloy", input="Hello from the streaming API!", response_format="mp3", extra_body={"stream": True}, ) as response: with open("output.mp3", "wb") as f: for chunk in response.iter_bytes(chunk_size=4096): f.write(chunk) ``` ### Compare TTFB (streaming vs non-streaming) ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/audio/speech \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini-tts","input":"A longer sentence to demonstrate the streaming improvement.","voice":"alloy","stream":true}' \ -o streaming.mp3 \ -w "\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" ``` ## Notes & Limits * Max input length: depends on model; measured in characters or tokens. For short, interactive prompts, prefer under \~1-2k characters. * Typical latency: scales with input length and output format; compressed formats like `mp3` are often faster than `wav`. * Usage metering: billed by input characters for TTS models; output file size does not affect billing. ## Audio Format Support by Provider | Provider | Supported `response_format` values | | ----------------------------------------------------- | ---------------------------------------------------------------- | | OpenAI (`tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`) | `mp3` (default), `opus`, `aac`, `flac`, `wav`, `pcm` | | ElevenLabs (`Elevenlabs-Turbo-V2.5`, `Elevenlabs-V3`) | Always returns `audio/mpeg` (MP3). `response_format` is ignored. | ## Voices * Voice IDs vary by model/provider. See model-specific voices on Text-to-Speech: `api-reference/text-to-speech.mdx`. * If a voices listing endpoint is available (for example `GET /v1/voices`), it returns available voice IDs and metadata (language coverage, gender/pitch, sample links). ## Errors & Troubleshooting * `invalid_model`, `invalid_voice`, `unsupported_format`: Verify `model`, `voice`, and `response_format`. * `input_too_long`: Reduce length; split long text into chunks and stitch audio client-side. * `rate_limit_exceeded`: Exponential backoff; retry after the window resets. * Network/client tips: set `Accept` to your preferred audio type and write raw response bytes directly to a file/stream. ## Security * Do not expose API keys in browsers. Proxy via your server. * Redact PII in logs; avoid logging raw text/audio in production. * Rate-limit public routes. ## Pricing, Quotas, and Rate Limits * Billing is based on input character count, not output audio size. * For streaming requests, billing is recorded after the first audio chunk is confirmed. If the upstream provider fails before any audio is produced, no charge is applied. * If the client disconnects mid-stream after audio starts, the charge still applies because generation already began upstream. * Rate limits: per-minute/day caps; contact support to request increases. See `api-reference/miscellaneous/pricing.mdx` and `api-reference/miscellaneous/rate-limits.mdx`. ## Migration from Job-based TTS Already using the async `POST /tts` + `GET /tts/status` flow? * When to switch: choose `v1/audio/speech` for short prompts, low latency, and direct playback; keep job-based TTS for long/batch generation and webhook workflows. * Parameter mapping: `text` -> `input`, `voice` stays `voice`, and output format can be requested with `response_format` when supported. * Retries/timeouts: `v1/audio/speech` returns inline; implement client-side timeouts and simple retries on 5xx. ## See Also * Async/job-based TTS: `api-reference/endpoint/tts.mdx` * TTS Status polling: `api-reference/endpoint/tts-status.mdx` # Subscription Usage Source: https://docs.nano-gpt.com/api-reference/endpoint/subscription-usage GET /subscription/v1/usage ## Overview Returns subscription status and current daily/monthly usage for the active billing period. ## Request * Method: `GET` * Path: `/api/subscription/v1/usage` * Auth: `Authorization: Bearer ` or `x-api-key: ` ## Response `200 application/json`. Timestamps are UNIX epoch milliseconds. ```json theme={null} { "active": true, "limits": { "daily": 5000, "monthly": 60000 }, "enforceDailyLimit": true, "daily": { "used": 5, "remaining": 4995, "percentUsed": 0.001, "resetAt": 1738540800000 }, "monthly": { "used": 45, "remaining": 59955, "percentUsed": 0.00075, "resetAt": 1739404800000 }, "period": { "currentPeriodEnd": "2025-02-13T23:59:59.000Z" }, "state": "active", "graceUntil": null } ``` Fields * `active` — Whether the account is currently active for subscription usage. * `limits.daily`, `limits.monthly` — Configured daily/monthly allowance. * `enforceDailyLimit` — Always `true` for subscriptions. Daily and monthly limits are both enforced and the daily limit cannot be disabled. * `daily.used`, `monthly.used` — Usage units consumed in the current day/month window. * `daily.remaining`, `monthly.remaining` — Remaining allowance for each window. * `daily.percentUsed`, `monthly.percentUsed` — Decimal fraction in \[0,1]. * `daily.resetAt`, `monthly.resetAt` — Millisecond epoch when the window resets. * `period.currentPeriodEnd` — ISO timestamp for the end of the current billing period, if known. * `state` — One of `active`, `grace`, `inactive`. * `graceUntil` — ISO timestamp when grace access ends (if applicable). ## Usage semantics * Usage units represent successful subscription‑covered operations (e.g., a completed generation). They are not tokens or dollar cost. * Daily window resets at the next UTC day start; monthly usage aligns to the subscription billing cycle when available. Daily limits are always enforced. ## Examples ```bash cURL theme={null} curl -s \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ https://nano-gpt.com/api/subscription/v1/usage | jq ``` ```ts JavaScript/TypeScript theme={null} const res = await fetch('https://nano-gpt.com/api/subscription/v1/usage', { headers: { 'Authorization': `Bearer ${NANOGPT_API_KEY}` }, }); const data = await res.json(); ``` # TEE Attestation Source: https://docs.nano-gpt.com/api-reference/endpoint/tee-attestation GET /v1/tee/attestation Fetch TEE attestation report for a model Fetch a TEE attestation report for a given model. # TEE Signature Source: https://docs.nano-gpt.com/api-reference/endpoint/tee-signature GET /v1/tee/signature/{requestId} Fetch ECDSA signature for a chat request Fetch an ECDSA signature for a chat request executed in a TEE. # Speech-to-Text Transcription Source: https://docs.nano-gpt.com/api-reference/endpoint/transcribe POST /transcribe Transcribe audio (and supported video formats) into text using speech recognition models. Supports multiple languages, diarization (model-dependent), and various formats. Most models return synchronous results; some models (for example Elevenlabs-STT and voice cloning workflows) return asynchronous job IDs. ## Overview The Speech-to-Text transcription endpoint converts audio files into text using state-of-the-art speech recognition models. Supports multiple languages, speaker diarization, and various audio formats. Looking for a drop-in OpenAI-compatible STT endpoint? Use `POST /api/v1/audio/transcriptions`. See `api-reference/endpoint/audio-transcriptions.mdx`. ## Supported Models * **Whisper-Large-V3**: High-accuracy transcription (\~\$0.0005/min) - Synchronous * **Wizper**: Fast and efficient transcription (\$0.01/min) - Synchronous * **Elevenlabs-STT**: Premium transcription with diarization (\$0.03/min) - Asynchronous * **gpt-4o-mini-transcribe**: Efficient OpenAI transcription (\$0.003/min) - Synchronous * **gpt-4o-mini-transcribe-2025-03-20**: Snapshot (\$0.003/min) - Synchronous * **gpt-4o-mini-transcribe-2025-12-15**: Snapshot (\$0.003/min) - Synchronous * **gpt-4o-mini-transcribe-latest**: Alias (\$0.003/min) - Synchronous * **openai-whisper-with-video**: Video-to-text transcription (\$0.06/min) - Synchronous * **qwen-voice-clone**: Voice cloning (\$0.25/run) - Asynchronous * **minimax-voice-clone**: Voice cloning (\$1.00/run) - Asynchronous ## Upload Methods ### Direct File Upload (≤3MB) ```python Python theme={null} import requests def transcribe_file(file_path): headers = {"x-api-key": "YOUR_API_KEY"} with open(file_path, 'rb') as audio_file: files = {'audio': ('audio.mp3', audio_file, 'audio/mpeg')} data = { 'model': 'Whisper-Large-V3', 'language': 'en' } response = requests.post( "https://nano-gpt.com/api/transcribe", headers=headers, files=files, data=data ) return response.json() result = transcribe_file("meeting.mp3") print(result['transcription']) ``` ```javascript JavaScript theme={null} const formData = new FormData(); formData.append('audio', audioFile); formData.append('model', 'Whisper-Large-V3'); formData.append('language', 'auto'); const response = await fetch('https://nano-gpt.com/api/transcribe', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY' }, body: formData }); const result = await response.json(); console.log(result.transcription); ``` ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/transcribe \ -H "x-api-key: YOUR_API_KEY" \ -F "audio=@meeting.mp3" \ -F "model=Whisper-Large-V3" \ -F "language=en" ``` ### URL Upload (≤500MB) ```python Python theme={null} import requests def transcribe_url(audio_url): headers = { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "audioUrl": audio_url, "model": "Wizper", "language": "auto" } response = requests.post( "https://nano-gpt.com/api/transcribe", headers=headers, json=data ) return response.json() result = transcribe_url("https://example.com/audio.mp3") print(result['transcription']) ``` ```javascript JavaScript theme={null} const response = await fetch('https://nano-gpt.com/api/transcribe', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ audioUrl: 'https://example.com/audio.mp3', model: 'Wizper', language: 'auto' }) }); const result = await response.json(); console.log(result.transcription); ``` ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/transcribe \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "audioUrl": "https://example.com/audio.mp3", "model": "Wizper", "language": "auto" }' ``` ## Advanced Features - Speaker Diarization Use Elevenlabs-STT for speaker identification (asynchronous processing): ```python Python theme={null} import requests import time def transcribe_with_speakers(audio_url): headers = { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" } # Submit transcription job data = { "audioUrl": audio_url, "model": "Elevenlabs-STT", "diarize": True, "tagAudioEvents": True } response = requests.post( "https://nano-gpt.com/api/transcribe", headers=headers, json=data ) if response.status_code == 202: job_data = response.json() # Poll for results status_data = { "runId": job_data['runId'], "cost": job_data.get('cost'), "paymentSource": job_data.get('paymentSource'), "isApiRequest": True } while True: status_response = requests.post( "https://nano-gpt.com/api/transcribe/status", headers=headers, json=status_data ) result = status_response.json() if result.get('status') == 'completed': return result elif result.get('status') == 'failed': raise Exception(f"Transcription failed: {result.get('error')}") time.sleep(5) result = transcribe_with_speakers("https://example.com/meeting.mp3") # Access speaker segments for segment in result['diarization']['segments']: print(f"{segment['speaker']}: {segment['text']}") ``` ## Language Support Supports 97+ languages with auto-detection: ```python theme={null} # Common language codes languages = { "auto": "Auto-detect", "en": "English", "es": "Spanish", "fr": "French", "de": "German", "zh": "Chinese", "ja": "Japanese", "ar": "Arabic" } ``` ## Response Examples ### Synchronous Response (most models) ```json theme={null} { "transcription": "Hello, this is a test transcription.", "metadata": { "fileName": "audio.mp3", "fileSize": 1234567, "chargedDuration": 2.5, "actualDuration": 2.5, "language": "en", "cost": 0.0012, "currency": "USD", "model": "Whisper-Large-V3" } } ``` ### Asynchronous Response (Elevenlabs-STT and voice cloning) Initial response (202): ```json theme={null} { "runId": "abc123def456", "status": "pending", "model": "Elevenlabs-STT", "cost": 0.075, "paymentSource": "USD" } ``` Final response (when completed): ```json theme={null} { "status": "completed", "transcription": "Speaker 1: Hello everyone. Speaker 2: Hi there!", "metadata": { ... }, "diarization": { "segments": [ { "speaker": "Speaker 1", "text": "Hello everyone", "start": 0.5, "end": 1.5 } ] }, "words": [ { "text": "Hello", "start": 0.5, "end": 0.9, "type": "word", "speaker_id": "speaker_0" } ] } ``` # Speech-to-Text Status Source: https://docs.nano-gpt.com/api-reference/endpoint/transcribe-status POST /transcribe/status Check the status of an asynchronous transcription job (Elevenlabs-STT). Poll this endpoint to get the transcription results when the job is completed. ## Overview Check the status of an asynchronous transcription job (Elevenlabs-STT). Poll this endpoint to get transcription results when the job is completed. ## Usage This endpoint is used with the Elevenlabs-STT model which processes transcriptions asynchronously. After submitting a transcription job, you'll receive a `runId` that you use to check the status. ```python Python theme={null} import requests import time def check_transcription_status(run_id, job_data): headers = { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" } status_data = { "runId": run_id, "cost": job_data.get('cost'), "paymentSource": job_data.get('paymentSource'), "isApiRequest": True, "fileName": job_data.get('fileName'), "fileSize": job_data.get('fileSize'), "chargedDuration": job_data.get('chargedDuration'), "diarize": job_data.get('diarize', False) } response = requests.post( "https://nano-gpt.com/api/transcribe/status", headers=headers, json=status_data ) return response.json() def wait_for_completion(run_id, job_data, max_attempts=60): for attempt in range(max_attempts): result = check_transcription_status(run_id, job_data) status = result.get('status') if status == 'completed': return result elif status == 'failed': raise Exception(f"Transcription failed: {result.get('error')}") print(f"Status: {status} (attempt {attempt + 1}/{max_attempts})") time.sleep(5) raise Exception("Transcription timed out") # Usage job_data = {"runId": "abc123", "cost": 0.075, "paymentSource": "USD"} result = wait_for_completion("abc123", job_data) print(result['transcription']) ``` ```javascript JavaScript theme={null} async function checkTranscriptionStatus(runId, jobData) { const response = await fetch('https://nano-gpt.com/api/transcribe/status', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ runId: runId, cost: jobData.cost, paymentSource: jobData.paymentSource, isApiRequest: true, fileName: jobData.fileName, fileSize: jobData.fileSize, chargedDuration: jobData.chargedDuration, diarize: jobData.diarize || false }) }); return await response.json(); } async function waitForCompletion(runId, jobData, maxAttempts = 60) { for (let attempt = 0; attempt < maxAttempts; attempt++) { const result = await checkTranscriptionStatus(runId, jobData); const status = result.status; if (status === 'completed') { return result; } else if (status === 'failed') { throw new Error(`Transcription failed: ${result.error}`); } console.log(`Status: ${status} (attempt ${attempt + 1}/${maxAttempts})`); await new Promise(resolve => setTimeout(resolve, 5000)); } throw new Error('Transcription timed out'); } // Usage const jobData = {runId: "abc123", cost: 0.075, paymentSource: "USD"}; const result = await waitForCompletion("abc123", jobData); console.log(result.transcription); ``` ```bash cURL theme={null} # Check status curl -X POST https://nano-gpt.com/api/transcribe/status \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "runId": "abc123def456", "cost": 0.075, "paymentSource": "USD", "isApiRequest": true, "fileName": "meeting.mp3", "fileSize": 2345678, "chargedDuration": 2.5, "diarize": true }' ``` ## Status Values * **`pending`**: Job is queued for processing * **`processing`**: Transcription is in progress * **`completed`**: Transcription finished successfully * **`failed`**: Transcription failed (check error field) ## Response Examples ### Pending/Processing ```json theme={null} { "status": "processing" } ``` ### Completed ```json theme={null} { "status": "completed", "transcription": "Speaker 1: Hello everyone. Speaker 2: Hi there!", "metadata": { "fileName": "meeting.mp3", "fileSize": 2345678, "chargedDuration": 2.5, "actualDuration": 2.47, "language": "en", "cost": 0.075, "currency": "USD", "model": "Elevenlabs-STT" }, "words": [ { "text": "Hello", "start": 0.5, "end": 0.9, "type": "word", "speaker_id": "speaker_0" } ], "diarization": { "segments": [ { "speaker": "Speaker 1", "text": "Hello everyone", "start": 0.5, "end": 1.5 } ] } } ``` ### Failed ```json theme={null} { "status": "failed", "error": "Audio file could not be processed" } ``` # Text-to-Speech Source: https://docs.nano-gpt.com/api-reference/endpoint/tts POST /tts Convert text into natural-sounding speech using various TTS models from different providers. Supports multiple languages, voices, and customization options including speed control, voice instructions, and audio format selection. ## Overview Convert text into natural-sounding speech using various TTS models. Supports multiple languages, voices, and customization options including speed control and voice instructions. Looking for synchronous, low‑latency TTS that returns audio bytes directly? See [Speech](/api-reference/endpoint/speech) (POST `/v1/audio/speech`). Want to clone a custom voice from a reference audio clip? See [Voice Cloning](/api-reference/endpoint/voice-cloning). ## Supported Models * **Kokoro-82m**: 44 multilingual voices (\$0.001/1k chars) * **Elevenlabs-Turbo-V2.5**: Premium quality with style controls (\$0.06/1k chars) * **tts-1**: OpenAI standard quality (\$0.015/1k chars) * **tts-1-hd**: OpenAI high definition (\$0.030/1k chars) * **gpt-4o-mini-tts**: Ultra-low cost (\$0.0006/1k chars) * **MiniMax Speech models**: Supports cloned voices via custom voice IDs (see [Voice Cloning](/api-reference/endpoint/voice-cloning)) * **Qwen-3-TTS-1.7B**: Supports cloned voices via speaker embeddings (see [Voice Cloning](/api-reference/endpoint/voice-cloning)) ## Basic Usage ```python Python theme={null} import requests def text_to_speech(text, model="Kokoro-82m", voice=None, **kwargs): headers = { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" } payload = { "text": text, "model": model } if voice: payload["voice"] = voice payload.update(kwargs) response = requests.post( "https://nano-gpt.com/api/tts", headers=headers, json=payload ) if response.status_code == 200: content_type = response.headers.get('content-type', '') if 'application/json' in content_type: # JSON response with audio URL data = response.json() audio_response = requests.get(data['audioUrl']) with open('output.wav', 'wb') as f: f.write(audio_response.content) else: # Binary audio data (OpenAI models) with open('output.mp3', 'wb') as f: f.write(response.content) return response else: raise Exception(f"Error: {response.status_code}") # Basic usage text_to_speech( "Hello! Welcome to our service.", model="Kokoro-82m", voice="af_bella" ) ``` ```javascript JavaScript theme={null} async function textToSpeech(text, options = {}) { const payload = { text: text, model: options.model || 'Kokoro-82m', ...options }; const response = await fetch('https://nano-gpt.com/api/tts', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (response.ok) { const contentType = response.headers.get('content-type'); if (contentType.includes('application/json')) { const data = await response.json(); console.log('Audio URL:', data.audioUrl); return data; } else { // Binary audio data const audioBlob = await response.blob(); const url = URL.createObjectURL(audioBlob); console.log('Audio blob URL:', url); return { audioBlob, url }; } } else { throw new Error(`Error: ${response.status}`); } } // Usage textToSpeech('Hello world!', { model: 'Kokoro-82m', voice: 'af_bella', speed: 1.1 }); ``` ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/tts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello! Welcome to our service.", "model": "Kokoro-82m", "voice": "af_bella", "speed": 1.0 }' ``` ## Async Status and Result Retrieval Some TTS models run asynchronously. When queued, the API returns HTTP 202 with a ticket containing a `runId` and `model`. Use the TTS Status endpoint to poll until the job is complete. Synchronous models return audio immediately and do not require status polling. ### Endpoints * Submit TTS: `POST /api/tts` * Check TTS Status (async only): `GET /api/tts/status?runId=...&model=...` ### When you see status: "pending" If your initial `POST /api/tts` returns HTTP 202 with a body like: ```json theme={null} { "status": "pending", "runId": "98b0d593-fe8d-49b8-89c9-233022232297", "model": "Elevenlabs-Turbo-V2.5", "charged": true, "cost": 0.0050388, "paymentSource": "USD", "isApiRequest": true } ``` …the request is queued. Poll the Status endpoint using the `runId` and `model`. If present, include `cost`, `paymentSource`, and `isApiRequest` from the ticket when polling to help with automatic refunds if the upstream provider later rejects content. ### cURL — Submit, then Poll ```bash cURL theme={null} # 1) Submit TTS curl -X POST https://nano-gpt.com/api/tts \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "text": "Hello there!", "model": "Elevenlabs-Turbo-V2.5", "voice": "Rachel", "speed": 1.0 }' # 2) If response is 202/pending, poll using returned values curl "https://nano-gpt.com/api/tts/status?runId=98b0d593-fe8d-49b8-89c9-233022232297&model=Elevenlabs-Turbo-V2.5&cost=0.0050388&paymentSource=USD&isApiRequest=true" \ -H 'x-api-key: YOUR_API_KEY' # 3) On completion, you'll receive an audioUrl # { # "status": "completed", # "audioUrl": "https://.../file.mp3", # "contentType": "audio/mpeg", # "model": "Elevenlabs-Turbo-V2.5" # } ``` ```javascript JavaScript theme={null} async function submitTTS({ text, model = 'Elevenlabs-Turbo-V2.5', voice = 'Rachel', speed = 1 }) { const res = await fetch('https://nano-gpt.com/api/tts', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ text, model, voice, speed }) }); if (res.status === 202) { const ticket = await res.json(); return await pollTTSStatus(ticket); } if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error || 'TTS request failed'); } // Synchronous: either JSON with URL or binary audio const ct = res.headers.get('content-type') || ''; if (ct.includes('application/json')) { const json = await res.json(); return json.audioUrl; } const blob = await res.blob(); return URL.createObjectURL(blob); // play via ### Synchronous vs. Asynchronous Models * Synchronous models (examples: `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`, `Kokoro-82m`) return immediately from `POST /api/tts` with either binary audio or JSON containing `{ audioUrl, contentType }` depending on the provider. * Asynchronous models (examples: `Elevenlabs-Turbo-V2.5`, `Elevenlabs-V3`, `Elevenlabs-Music-V1`) return HTTP 202 with a polling ticket. Use `GET /api/tts/status` until completed. For OpenAI-compatible music generation via `POST /api/v1/audio/speech`, see [Music Generation](/api-reference/music-generation). ### Best Practices * Poll every 2–3 seconds; stop after 2–3 minutes and show a timeout error. * Always include `runId` and `model`. If available, include `cost`, `paymentSource`, and `isApiRequest` from the ticket for better error handling and refund automation. * On `completed`, prefer using the `audioUrl` directly (streaming or download). Cache URLs client‑side if you plan to replay. * If you receive `CONTENT_POLICY_VIOLATION`, do not retry the same content; surface a clear message to the user. ### FAQ * Why did I get 202/pending? The selected model runs asynchronously; your request was queued and billed after a successful queue submission. * Can I cancel a pending TTS? Not currently. Let it complete or time out client‑side. * Do all TTS models require polling? No. Only async models. Synchronous models return immediately. ## Model-Specific Examples ### Kokoro-82m - Multilingual Voices 44 voices across 13 language groups: ```python Python theme={null} # Popular voice examples by category voices = { "american_female": ["af_bella", "af_nova", "af_aoede"], "american_male": ["am_adam", "am_onyx", "am_eric"], "british_female": ["bf_alice", "bf_emma"], "british_male": ["bm_daniel", "bm_george"], "japanese_female": ["jf_alpha", "jf_gongitsune"], "chinese_female": ["zf_xiaoxiao", "zf_xiaoyi"], "french_female": ["ff_siwis"], "italian_male": ["im_nicola"] } # Generate multilingual samples samples = [ {"text": "Hello, welcome!", "voice": "af_bella", "lang": "English"}, {"text": "Bonjour et bienvenue!", "voice": "ff_siwis", "lang": "French"}, {"text": "こんにちは!", "voice": "jf_alpha", "lang": "Japanese"}, {"text": "你好,欢迎!", "voice": "zf_xiaoxiao", "lang": "Chinese"} ] for sample in samples: text_to_speech( text=sample["text"], model="Kokoro-82m", voice=sample["voice"] ) ``` ### Elevenlabs-Turbo-V2.5 - Advanced Voice Controls Premium quality with style adjustments: ```python Python theme={null} # Stable, consistent voice text_to_speech( text="This is a professional announcement.", model="Elevenlabs-Turbo-V2.5", voice="Rachel", stability=0.9, similarity_boost=0.8, style=0 ) # Expressive, dynamic voice text_to_speech( text="This is so exciting!", model="Elevenlabs-Turbo-V2.5", voice="Rachel", stability=0.3, similarity_boost=0.7, style=0.8, speed=1.2 ) # Available voices: Rachel, Adam, Bella, Brian, etc. ``` ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/tts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Welcome to our premium service!", "model": "Elevenlabs-Turbo-V2.5", "voice": "Rachel", "stability": 0.7, "similarity_boost": 0.8, "style": 0.3 }' ``` ### OpenAI Models - Multiple Formats & Instructions ```python Python theme={null} # High-definition with voice instructions text_to_speech( text="Welcome to customer service.", model="tts-1-hd", voice="nova", instructions="Speak warmly and professionally like a customer service representative", response_format="flac" ) # Ultra-low cost option text_to_speech( text="This is a cost-effective option.", model="gpt-4o-mini-tts", voice="alloy", instructions="Speak clearly and cheerfully", response_format="mp3" ) # Different format examples formats = ["mp3", "wav", "opus", "flac", "aac"] for fmt in formats: text_to_speech( text=f"This is {fmt.upper()} format.", model="tts-1", voice="echo", response_format=fmt ) ``` ```bash cURL theme={null} # With voice instructions curl -X POST https://nano-gpt.com/api/tts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Breaking news update!", "model": "tts-1-hd", "voice": "nova", "instructions": "Speak with the urgency of a news reporter", "response_format": "wav" }' # Ultra-low cost curl -X POST https://nano-gpt.com/api/tts \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Budget-friendly text-to-speech.", "model": "gpt-4o-mini-tts", "voice": "alloy" }' ``` ## Response Examples ### JSON Response (Most Models) ```json theme={null} { "audioUrl": "https://storage.url/audio-file.wav", "contentType": "audio/wav", "model": "Kokoro-82m", "text": "Hello world", "voice": "af_bella", "speed": 1, "duration": 2.3, "cost": 0.001, "currency": "USD" } ``` ### Binary Response (OpenAI Models) OpenAI models return audio data directly as binary with appropriate headers: ```http theme={null} Content-Type: audio/mp3 Content-Length: 123456 [Binary audio data] ``` ## Voice Options ### Kokoro-82m Voices * **American Female**: af\_bella, af\_nova, af\_aoede, af\_jessica, af\_sarah * **American Male**: am\_adam, am\_onyx, am\_eric, am\_liam * **British**: bf\_alice, bf\_emma, bm\_daniel, bm\_george * **Asian Languages**: jf\_alpha (Japanese), zf\_xiaoxiao (Chinese) * **European**: ff\_siwis (French), im\_nicola (Italian) ### Elevenlabs-Turbo-V2.5 Voices Rachel, Adam, Bella, Brian, Sarah, Michael, Emily, James, Nicole, and 37 more ### OpenAI Voices alloy, echo, fable, onyx, nova, shimmer, ash, ballad, coral, sage, verse ## Error Handling ```python Python theme={null} try: result = text_to_speech("Hello world!", model="Kokoro-82m") print("Success!") except Exception as e: if "400" in str(e): print("Bad request - check parameters") elif "401" in str(e): print("Unauthorized - check API key") elif "413" in str(e): print("Text too long for model") else: print(f"Error: {e}") ``` Common errors: * **400**: Invalid parameters or missing text * **401**: Invalid or missing API key * **413**: Text exceeds model character limit * **429**: Rate limit exceeded # TTS Status Source: https://docs.nano-gpt.com/api-reference/endpoint/tts-status GET /tts/status ## Overview Poll the status of an asynchronous text-to-speech (TTS) job. Use the `runId` and `model` values returned by `POST /api/tts` when the initial response is `202` with `status: "pending"`. For low‑latency, synchronous TTS without polling, use [Speech](/api-reference/endpoint/speech) (POST `/v1/audio/speech`). ### Query parameters * `runId` (string, required): Job identifier from the submit response * `model` (string, required): The model that was used for the job (e.g., `Elevenlabs-Turbo-V2.5`) * `cost` (number, optional): Cost from the ticket; helps with automatic refunds * `paymentSource` (string, optional): Currency/source from the ticket (e.g., `USD`) * `isApiRequest` (boolean, optional): Pass `true` when polling from API clients Including `cost`, `paymentSource`, and `isApiRequest` from the original ticket helps the platform perform automatic refunds if the upstream provider rejects content after you were charged. ## Usage ```python Python theme={null} import time import requests BASE = "https://nano-gpt.com/api" def get_tts_status(run_id: str, model: str, api_key: str, *, cost=None, payment_source=None, is_api_request=True) -> dict: params = {"runId": run_id, "model": model} if isinstance(cost, (int, float)): params["cost"] = str(cost) if payment_source: params["paymentSource"] = str(payment_source) if isinstance(is_api_request, bool): params["isApiRequest"] = str(is_api_request) resp = requests.get( f"{BASE}/tts/status", headers={"x-api-key": api_key}, params=params, timeout=30, ) resp.raise_for_status() return resp.json() def wait_for_tts(run_id: str, model: str, api_key: str, *, cost=None, payment_source=None, is_api_request=True, max_attempts: int = 60, delay_s: int = 3) -> str: for attempt in range(max_attempts): data = get_tts_status(run_id, model, api_key, cost=cost, payment_source=payment_source, is_api_request=is_api_request) status = data.get("status") if status == "completed" and data.get("audioUrl"): return data["audioUrl"] if status == "error": raise RuntimeError(data.get("error", "TTS generation failed")) time.sleep(delay_s) raise TimeoutError("Polling timeout") ``` ```javascript JavaScript theme={null} const BASE = 'https://nano-gpt.com/api'; async function getTTSStatus({ runId, model, cost, paymentSource, isApiRequest = true }, apiKey) { const qs = new URLSearchParams({ runId, model }); if (typeof cost === 'number') qs.set('cost', String(cost)); if (paymentSource) qs.set('paymentSource', String(paymentSource)); if (typeof isApiRequest === 'boolean') qs.set('isApiRequest', String(isApiRequest)); const res = await fetch(`${BASE}/tts/status?${qs.toString()}`, { headers: { 'x-api-key': apiKey } }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error || `Polling failed (${res.status})`); } return res.json(); } export async function waitForTTS(ticket, apiKey, maxAttempts = 60, delayMs = 3000) { for (let i = 0; i < maxAttempts; i++) { const data = await getTTSStatus(ticket, apiKey); if (data.status === 'completed' && data.audioUrl) return data.audioUrl; if (data.status === 'error') throw new Error(data.error || 'TTS generation failed'); await new Promise(r => setTimeout(r, delayMs)); } throw new Error('Polling timeout'); } ``` ```bash cURL theme={null} # Single status check curl -s "https://nano-gpt.com/api/tts/status?runId=RUN_ID&model=Elevenlabs-Turbo-V2.5&cost=0.0050388&paymentSource=USD&isApiRequest=true" \ -H "x-api-key: YOUR_API_KEY" | jq . # Simple poll (every 3s, 3 minutes max) for i in {1..60}; do RESP=$(curl -s "https://nano-gpt.com/api/tts/status?runId=RUN_ID&model=Elevenlabs-Turbo-V2.5" -H "x-api-key: YOUR_API_KEY") STATUS=$(echo "$RESP" | jq -r '.status // empty') echo "Attempt $i: status=$STATUS" if [ "$STATUS" = "completed" ]; then echo "$RESP" | jq . break fi if [ "$STATUS" = "error" ]; then echo "$RESP" | jq . exit 1 fi sleep 3 done ``` ## Response examples ### Pending ```json theme={null} { "status": "pending", "runId": "98b0d593-fe8d-49b8-89c9-233022232297", "queuePosition": 3 } ``` ### Completed ```json theme={null} { "status": "completed", "audioUrl": "https://.../file.mp3", "contentType": "audio/mpeg", "model": "Elevenlabs-Turbo-V2.5" } ``` ### Error (generic) ```json theme={null} { "status": "error", "error": "TTS generation failed. Please try again." } ``` ### Error (content policy) ```json theme={null} { "status": "error", "code": "CONTENT_POLICY_VIOLATION", "error": "Content rejected by provider. Please modify your prompt and try again." } ``` ### Notes * For Elevenlabs‑family async models (e.g., `Elevenlabs-Turbo-V2.5`, `Elevenlabs-V3`) you will always poll this endpoint until it returns `completed`. * When the job completes, the response includes an `audioUrl` you can download or play in the browser. * If available, include `cost`, `paymentSource`, and `isApiRequest` from the original ticket while polling to improve refund handling. # Usage Source: https://docs.nano-gpt.com/api-reference/endpoint/usage GET /v1/usage Retrieve aggregate spend, request, and token usage for the authenticated API key ## Overview The Usage API returns bounded aggregate usage for the API key used to authenticate the request. Use it to answer: * What did this API key spend? * Which models was that spend on? * How many requests and tokens were used? * What happened over a specific UTC date range? The endpoint returns aggregate usage only. It does not return raw transaction rows, request payloads, prompts, responses, provider routing details, or account-wide usage across multiple API keys. ## Base URL ```text theme={null} https://nano-gpt.com/api/v1/usage ``` ## Authentication Use the same NanoGPT API key authentication as other API routes: ```http theme={null} Authorization: Bearer $NANOGPT_API_KEY ``` Example: ```bash theme={null} curl "https://nano-gpt.com/api/v1/usage" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ## Default Request If no date range is provided, the endpoint returns the authenticated API key's last 30 UTC days, grouped by both day and model. ```bash theme={null} curl "https://nano-gpt.com/api/v1/usage" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ## Date Ranges Use `from` and `to` to request an explicit UTC date range. Both values must be `YYYY-MM-DD` dates. `to` is inclusive. ```bash theme={null} curl "https://nano-gpt.com/api/v1/usage?from=2026-05-01&to=2026-05-31" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` Rules: * `from` and `to` must be provided together. * `to` must be on or after `from`. * `to` cannot be in the future. * Ranges are capped at 366 days. * All dates are interpreted in UTC. ## Grouping Use `group_by` to control which aggregate arrays are returned. | Value | Returned arrays | | ----------- | -------------------------------- | | `day` | `byDay` | | `model` | `byModel` | | `day,model` | `byDay`, `byModel`, `byDayModel` | `day,model` is the default. `model,day` is accepted as an alias for `day,model`. Example: ```bash theme={null} curl "https://nano-gpt.com/api/v1/usage?from=2026-05-01&to=2026-05-31&group_by=model" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ## Scope The endpoint is scoped to the authenticated API key. | Parameter | Default | Notes | | ------------ | ------------- | -------------------------------------------------------------------------- | | `scope` | `current_key` | `current_key` and `api_key` both return the authenticated API key's usage. | | `api_key_id` | current key | Optional current API key ID. Requests for another API key are rejected. | This endpoint does not return account-wide usage across all API keys. ## Response Shape ```json theme={null} { "object": "usage", "scope": "current_key", "apiKey": { "id": 123 }, "from": "2026-05-01", "to": "2026-05-31", "timezone": "UTC", "groupBy": "day,model", "asOf": "2026-05-31T12:00:00.000Z", "source": { "rollupDays": ["2026-05-01"], "liveDays": ["2026-05-31"], "missingRollupDays": [] }, "totals": { "requests": 1284, "costUsd": 42.18, "refundedUsd": 1.25, "netCostUsd": 40.93, "inputTokens": 1234567, "outputTokens": 456789, "reasoningTokens": 12000, "totalTokens": 1691356 }, "byDay": [ { "date": "2026-05-01", "requests": 61, "costUsd": 1.94, "refundedUsd": 0, "netCostUsd": 1.94, "inputTokens": 78000, "outputTokens": 21000, "reasoningTokens": 0, "totalTokens": 99000 } ], "byModel": [ { "model": "GPT-4.1 mini", "requests": 812, "costUsd": 18.92, "refundedUsd": 0.5, "netCostUsd": 18.42, "inputTokens": 900000, "outputTokens": 220000, "reasoningTokens": 0, "totalTokens": 1120000 } ], "byDayModel": [ { "date": "2026-05-01", "model": "GPT-4.1 mini", "requests": 20, "costUsd": 0.64, "refundedUsd": 0, "netCostUsd": 0.64, "inputTokens": 30000, "outputTokens": 8000, "reasoningTokens": 0, "totalTokens": 38000 } ] } ``` ## Field Reference Top-level fields: | Field | Description | | -------------------------- | -------------------------------------------------------------------------------------------------- | | `object` | Always `usage`. | | `scope` | Echoes the requested scope, either `current_key` or `api_key`. | | `apiKey.id` | Numeric ID of the authenticated API key. | | `from` | UTC start date. | | `to` | UTC end date, inclusive. | | `timezone` | Always `UTC`. | | `groupBy` | The effective grouping mode. | | `asOf` | Timestamp when the aggregate response was generated. Cached responses can be up to 60 seconds old. | | `source.rollupDays` | Days served from precomputed daily rollups. | | `source.liveDays` | Days served from live aggregation. Usually today or days not rolled up yet. | | `source.missingRollupDays` | Closed UTC days that were not available in the rollup table and had to be served live. | Usage counter fields: | Field | Description | | ----------------- | ---------------------------------------------------------------------------------------------------------- | | `requests` | Number of billable usage requests in the aggregate bucket. | | `costUsd` | Gross USD usage cost before refunds. | | `refundedUsd` | USD amount refunded in the aggregate bucket. | | `netCostUsd` | `max(0, costUsd - refundedUsd)` for that aggregate bucket. | | `inputTokens` | Input tokens counted for the bucket. | | `outputTokens` | Output tokens counted for the bucket. | | `reasoningTokens` | Reasoning tokens counted separately when available. | | `totalTokens` | `inputTokens + outputTokens`. Reasoning tokens are reported separately and are not added to `totalTokens`. | Model fields: * `model` values are public model labels. * Internal routing providers are not exposed. * Variants that share the same public label may be combined under that label. ## Refund Notes Refunds are applied at each returned aggregation level with: ```text theme={null} netCostUsd = max(0, costUsd - refundedUsd) ``` Because net cost is floored per bucket, sums of `byModel` or `byDayModel` rows may differ slightly from `totals.netCostUsd` when refunds cross buckets. ## Errors Errors use the standard NanoGPT API error shape: ```json theme={null} { "error": { "message": "from and to must use YYYY-MM-DD UTC dates.", "type": "invalid_request_error" } } ``` Common errors: | Status | Type | Meaning | | ------ | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `400` | `invalid_request_error` | Invalid date range, unsupported parameter, invalid grouping, or request for a different API key. | | `401` | `missing_api_key` / `invalid_api_key` | Missing or invalid API key. | | `429` | `rate_limit_exceeded` | Too many usage requests. | | `503` | `usage_not_ready` | The requested range needs rollup data that is not ready yet. Try a shorter range or retry after rollup sync completes. | | `503` | `service_unavailable` | Usage API temporarily unavailable. | | `500` | `server_error` | Unexpected usage retrieval failure. | ## Legacy Parameter Behavior The usage endpoint is aggregate-first and rejects legacy row-history parameters. Rejected legacy parameters include: * `duration` * `page` * `pageSize` * `sort` * `dir` * `filter` * `weekOffset` * `tz` * `include_summary` Use explicit UTC `from` and `to` dates instead. # Video Content Source: https://docs.nano-gpt.com/api-reference/endpoint/video-content GET /generate-video/content Proxy content retrieval for Sora 2 videos. ## Overview Proxy content retrieval for Sora 2 videos. ### Endpoint ``` GET /api/generate-video/content ``` ### Query parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | -------------------------------------- | | `runId` | string | Yes | The run ID | | `model` | string | Yes | Must be `sora-2` | | `variant` | string | No | `video`, `thumbnail`, or `spritesheet` | ## Usage ```bash theme={null} curl -s "https://nano-gpt.com/api/generate-video/content?runId=RUN_ID&model=sora-2&variant=video" \ -H "x-api-key: YOUR_API_KEY" | jq . ``` # Video Extend Source: https://docs.nano-gpt.com/api-reference/endpoint/video-extend POST /generate-video/extend Extend a Midjourney video using a task-based flow (taskId + index). ## Overview This endpoint is the task-based extension flow for Midjourney videos. For all other extend models, use `POST /api/generate-video` with a source video. ### Endpoint ``` POST /api/generate-video/extend ``` ### Rate limit 20 requests/minute per IP ### Supported models (task-based) * `midjourney-video` (extend a Midjourney video created via `POST /api/generate-video`) ### Required fields (Midjourney extend) * `runId` (preferred) or `taskId` (legacy alias): the job ID from the original Midjourney video request * `index` (0-3) `index` maps to the 4 videos returned by the original Midjourney request. ### Request notes * Send the same authentication headers as `/api/generate-video`. * Session ownership is enforced; requests for jobs you do not own return `403`. * This endpoint does not accept `video`, `videoUrl`, `videoDataUrl`, or `videoAttachmentId`. * The response format matches the standard generation response (`runId`, `id`, `status`, `model`, `cost`). ## Source video extend (use `/api/generate-video`) Use `POST /api/generate-video` with an extend model and a source video input. **Extend model examples**: * `wan-wavespeed-22-spicy-extend` * `wan-wavespeed-25-extend` * `veo3-1-extend` * `veo3-1-fast-extend` * `bytedance-seedance-v1.5-pro-extend` **Required fields**: * `prompt` * `videoUrl` (or `videoDataUrl` / `videoAttachmentId`) **Notes**: * `video` is only accepted by specific models (for example, `wan-wavespeed-25-extend`). * `wan-wavespeed-22-spicy-extend` accepts `videoUrl`, `videoDataUrl`, or `videoAttachmentId` only. * Max source video length: 120 seconds. ### Example: Midjourney extend (task-based) ```json theme={null} { "runId": "vid_m1abc123def456", "index": 0 } ``` ### Example response ```json theme={null} { "success": true, "runId": "vid_m1abc123def456", "id": "vid_m1abc123def456", "taskId": "vid_m1abc123def456", "status": "pending", "model": "midjourney-video", "message": "Extending video 1 by 5 seconds...", "eta": 60 } ``` ### Example: Source video extend (`/api/generate-video`) ```json theme={null} { "model": "wan-wavespeed-22-spicy-extend", "prompt": "Extend the clip with smooth motion and warm light", "videoUrl": "https://example.com/source.mp4", "resolution": "480p", "duration": 5, "seed": -1 } ``` `seed` is model/provider-route dependent and may improve reproducibility where supported; it does not guarantee identical output. # Video Generation Source: https://docs.nano-gpt.com/api-reference/endpoint/video-generation POST /generate-video Generate videos using supported text-to-video, image-to-video, and video-to-video models. The response includes a runId and pending status; poll the status endpoint for completion. See the docs for the current model list and required inputs. > Image-conditioned models accept either `imageDataUrl` (base64) or a public `imageUrl`. The service uses the explicit value you provide before checking any saved attachments. ## Overview `POST /generate-video` submits an asynchronous job to create, extend, or edit a video. The endpoint responds immediately with `runId`, `id`, `model`, and `status: "pending"`. `runId` and `id` are the same NanoGPT job identifier (format `vid_...`). Poll the unified Video Status endpoint with that job ID until you receive final assets. Duration-based billing is assessed after completion. Errors include descriptive JSON payloads. Surface the `error.message` (and HTTP status) to help users correct content-policy or validation issues. ## Extend Workflows * **Midjourney extend (task-based)**: use `POST /api/generate-video/extend` with `runId` (preferred) or `taskId` (legacy alias) plus `index` (0-3). This flow does not accept `video`, `videoUrl`, `videoDataUrl`, or `videoAttachmentId`. * **Source video extend (extend models)**: use `POST /api/generate-video` with an extend model plus `prompt` and a source video (`videoUrl`, `videoDataUrl`, or `videoAttachmentId`). `video` is only accepted by select models (for example, `wan-wavespeed-25-extend`). Max source video length: 120 seconds. ## Request Schema Only include the fields required by your chosen `model`. Unknown keys are ignored, but some models fail when extra media fields are present. ### Core Fields | field | type | required | details | | --------------------------------------------- | ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `model` | string | yes | Video model ID. Model availability changes; discover models via `GET /api/v1/models?detailed=true`. | | `conversationUUID` | string | no | Attach the request to a conversation thread. | | `prompt` | string | conditional | Required for text-to-video and edit models unless a structured script is supplied. | | `negative_prompt` | string | no | Suppresses specific content. Respected by Veo, Wan, Runway, Pixverse, and other models noted below. | | `script` | string | conditional | LongStories models accept full scripts instead of relying on `prompt`. | | `storyConfig` | object | conditional | LongStories structured payload (e.g. scenes, narration, voice). | | `animation` | boolean | no | Enables animation for LongStories outputs. | | `language` | string | no | Output language for LongStories. | | `characters` | array | no | Character definitions for LongStories. | | `duration` | string | conditional | Seconds as a string (`"5"`, `"8"`, `"60"`). Limits vary per model; see individual entries. | | `seconds` | string | conditional | Sora-specific duration selector (`"4"`, `"8"`, `"12"`). | | `aspect_ratio` | string | conditional | Ratios such as `16:9`, `9:16`, `1:1`, `3:4`, `4:3`, `21:9`, `auto`. | | `orientation` | string | conditional | `landscape` or `portrait` for Sora and Wan text/image flows. | | `resolution` | string | conditional | Resolution tokens (`480p`, `580p`, `720p`, `1080p`, `1792x1024`, `2k`, `4k`). | | `size` | string | conditional | Output size preset (supported by select models). | | `mode` | string | no | Operation mode: `text-to-video`, `image-to-video`, `reference-to-video`, `video-edit`. | | `generateAudio` | boolean | no | Adds AI audio on Veo 3 and Lightricks models. Defaults to `false`. | | `enhancePrompt` | boolean | no | Optional Veo 3 prompt optimizer. Defaults to `false`. | | `pro_mode` / `pro` | boolean | no | High-quality toggle for Sora and Hunyuan families. Defaults to `false`. | | `enable_prompt_expansion` | boolean | no | Prompt booster for Wan/Seedance/Minimax variants. Disabled by default. | | `enable_safety_checker` | boolean | no | Optional safety checker toggle (supported by select models). | | `camera_fix` / `camera_fixed` / `cameraFixed` | boolean | no | Locks the virtual camera for Seedance and Wan variants. | | `seed` | number or string | no | Optional seed forwarded on model/provider routes that support it. This may improve reproducibility but does not guarantee identical output. | | `voiceId` | string | conditional | Alternate voice selector for lipsync models. | | `voice_id` | string | conditional | Required by `kling-lipsync-t2v`. | | `voice_language` | string | conditional | `en` or `zh` for `kling-lipsync-t2v`. | | `voice_speed` | number | conditional | Range `0.8-2.0` for `kling-lipsync-t2v`. | | `videoDuration` / `billedDuration` | number | no | Optional overrides for upscaler billing calculations. | | `adjust_fps_for_interpolation` | boolean | no | Optional toggle for interpolation-aware upscaling. Defaults to `false`. | ### Media Inputs | field | type | required | details | | ------------------- | ------ | ----------- | ---------------------------------------------------------------------------------------------------------------- | | `imageDataUrl` | string | conditional | Base64-encoded data URL. Recommended for private assets or files larger than 4 MB. | | `imageUrl` | string | conditional | HTTPS link to a source image. | | `imageAttachmentId` | string | conditional | Reference to a library-stored image. | | `image` | string | conditional | Alternate image field accepted by select models. Prefer `imageUrl` unless the model explicitly requires `image`. | | `reference_image` | string | conditional | Optional still image guiding `runwayml-gen4-aleph`. | | `referenceImages` | array | conditional | Multiple reference images for reference-to-video flows. | | `referenceVideos` | array | conditional | Multiple reference videos. | | `audioDataUrl` | string | conditional | Base64 data URL for audio-driven models. | | `audioDuration` | number | conditional | Duration of provided audio in seconds. | | `audioUrl` | string | conditional | HTTPS audio input. | | `audio` | string | conditional | Alternate audio field accepted by select models. Prefer `audioUrl` unless the model explicitly requires `audio`. | | `videoUrl` | string | conditional | HTTPS link to a source video (edit, extend, upscaler, or lipsync jobs). | | `videoDataUrl` | string | conditional | Base64 data URL for a source video. | | `video` | string | conditional | Alternate video field accepted by select models. Prefer `videoUrl` unless the model explicitly requires `video`. | | `videoAttachmentId` | string | conditional | Reference to a library-stored video. | | `swapImage` | string | conditional | Swap image (face-swap models). | | `targetVideo` | string | conditional | Target video (face-swap models). | | `targetFaceIndex` | number | no | Optional face index (face-swap models). | > Provide only the media fields that your target model expects. Extra media inputs often trigger validation errors. > Prefer `videoUrl` (camelCase) for source videos; only send `video` when the model explicitly requires it. ### Advanced Controls | field | type | models | | ------------------------------------------------------------------------------------------------ | ------- | --------------------------------------------------------------- | | `num_frames` | integer | Wan 2.2 families, Seedance 22 5B, Wan image-to-video. | | `frames_per_second` | integer | Wan 2.2 5B. | | `num_inference_steps` | integer | Wan 2.2 families. | | `guidance_scale` | number | Wan 2.2 5B. | | `shift` | number | Wan 2.2 5B. | | `interpolator_model` | string | Wan 2.2 5B. | | `num_interpolated_frames` | integer | Wan 2.2 5B. | | `movementAmplitude` | string | Select models (for example `auto`, `small`, `medium`, `large`). | | `motion` | string | Select models (for example `low`, `high`). | | `style` | string | Select models (style/preset strings). | | `effectType`, `effect`, `cameraMovement`, `motionMode`, `soundEffectSwitch`, `soundEffectPrompt` | varies | Pixverse v4.5/v5. | | `mode` | string | Select models (for example `animate`, `replace`). | | `prompt_optimizer` | boolean | Select models. | ## Model Discovery Video model IDs and supported fields change over time. Use `GET /api/v1/models?detailed=true` to discover the current list and select a model intended for video generation. Notes: * Different models accept different media inputs (for example `imageUrl` vs a source `videoUrl`) and may support different duration / resolution options. * If you see validation errors, first retry with only the minimal required fields for your chosen model. ## Async Processing & Status Polling * The submission response includes `{ runId, id, model, status: "pending" }` where `id` and `runId` are identical. * Poll `/api/video/status?requestId=` (or `runId`) until the job reaches `status: "COMPLETED"` or `status: "FAILED"`. The legacy `/api/generate-video/status` endpoint is deprecated. * Many jobs emit intermediate states (`queued`, `processing`, `generating`, `delivering`). Persist them if you need audit trails. * Failed jobs include an `error` object. Surface the message and adjust prompts or inputs before retrying. * Duration and resolution determine credit usage. ### Response example ```json theme={null} { "runId": "vid_m1abc123def456", "id": "vid_m1abc123def456", "status": "pending", "model": "veo2-video", "cost": 0.35, "paymentSource": "XNO", "remainingBalance": 12.5, "prechargeLabel": "string" } ``` ## Content & Safety Notes Some models may block prompts that violate content policies. Non-200 responses describe the violation reason; relay these messages verbatim to users or implement automated prompt adjustments. ## Next Steps * Poll the Video Status endpoint after every submission to retrieve final assets. * Keep customer-facing pricing tables in sync with the API behavior you observe in production. # Video Models Source: https://docs.nano-gpt.com/api-reference/endpoint/video-models GET https://nano-gpt.com/api/v1/video-models List available video models with generation capabilities and supported parameters ## Overview Use `GET /api/v1/video-models` to discover the currently available video models. Do not hardcode video model capabilities in your client; supported settings and availability can change. This endpoint is cacheable. Refresh it periodically and treat new fields as additive. ## Endpoint ```text theme={null} GET https://nano-gpt.com/api/v1/video-models ``` ## Authentication Authentication is optional. * `Authorization: Bearer YOUR_API_KEY` * `x-api-key: YOUR_API_KEY` ## Query Parameters | Parameter | Type | Default | Description | | ---------- | ------- | ------- | ----------------------------------------------------------------------------- | | `detailed` | boolean | `true` | Include names, descriptions, pricing, capabilities, and supported parameters. | ## Response ```json theme={null} { "object": "list", "data": [ { "id": "video-model-id", "object": "model", "name": "Display name", "description": "Model description", "architecture": { "modality": "video", "input_modalities": ["text", "image"], "output_modalities": ["video"] }, "pricing": { "currency": "USD" }, "capabilities": { "video_generation": true, "text_to_video": true, "image_to_video": true, "audio_generation": false }, "supported_parameters": {} } ], "meta": { "count": 1, "generated_at": "2026-05-07T12:00:00.000Z" } } ``` ## Supported Parameters Supported parameters vary by model. Common examples include: * `duration` * `aspect_ratio` * `resolution` * image input support * audio generation support * video extension support * video recovery support * prompt enhancement or safety controls Use the model's `supported_parameters` and `capabilities` objects to decide which controls to show and which request fields to send. ## Example ```bash theme={null} curl "https://nano-gpt.com/api/v1/video-models?detailed=true" ``` ## Notes * Use this endpoint instead of hardcoding media model capabilities. * The response is cacheable, but model availability can change. * Pricing structures vary by model, for example per generation, per second, duration tier, resolution tier, or feature-specific pricing. # Video Recover Source: https://docs.nano-gpt.com/api-reference/endpoint/video-recover GET /generate-video/recover Recover recent video generation runs for a user. ## Overview Recover recent video generation runs for a user. ### Endpoint ``` GET /api/generate-video/recover ``` ### Query parameters | Parameter | Type | Required | Description | | ------------------ | ------ | -------- | -------------------------------- | | `model` | string | No | Filter by model | | `limit` | number | No | Max results (default 10, max 50) | | `conversationUUID` | string | No | Filter by conversation | ### Rate limit 20 requests/minute per IP ## Usage ```bash theme={null} curl -s "https://nano-gpt.com/api/generate-video/recover?limit=10" \ -H "x-api-key: YOUR_API_KEY" | jq . ``` ## Response ```json theme={null} { "data": [ { "runId": "vid_m1abc123def456", "id": "vid_m1abc123def456", "model": "sora-2", "status": "completed", "createdAt": "2025-01-17T12:34:56.000Z", "conversationUUID": "b7c75a5e-1e2a-4d4f-9e5b-6c6e2e2f9a17" } ] } ``` Notes: * New runs return NanoGPT job IDs (`vid_...`); legacy runs may return provider request IDs. # Video Status (Unified) Source: https://docs.nano-gpt.com/api-reference/endpoint/video-status-unified GET /video/status Unified video status endpoint that works across all video backends. Check the status of a video generation request using only the request ID and receive normalized status information. ## Overview Check the status of an asynchronous video generation job using a single, provider-agnostic endpoint. This endpoint accepts NanoGPT job IDs (`vid_...`) and legacy provider request IDs. No `model` parameter is required. Session ownership is enforced (returns `403` when the job is not yours). ### Query parameters | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ------------------------------------------------------ | | `requestId` | string | Yes\* | The job ID (`vid_...`) or a legacy provider request ID | | `runId` | string | Yes\* | Alias for `requestId` | \*Send either `requestId` or `runId`. ### Authentication Provide `x-api-key` or a valid session cookie. ## Usage ```python Python theme={null} import requests import time BASE = "https://nano-gpt.com/api" def get_video_status(request_id: str, api_key: str) -> dict: resp = requests.get( f"{BASE}/video/status", headers={"x-api-key": api_key}, params={"requestId": request_id}, timeout=30, ) resp.raise_for_status() return resp.json() def wait_for_video(request_id: str, api_key: str, max_attempts: int = 120, delay_s: int = 5) -> str: for _ in range(max_attempts): payload = get_video_status(request_id, api_key) status = payload.get("data", {}).get("status") if status == "COMPLETED": return payload["data"]["output"]["video"]["url"] if status == "FAILED": raise RuntimeError(payload.get("data", {}).get("error", "Video generation failed")) time.sleep(delay_s) raise TimeoutError("Video generation timed out") ``` ```javascript JavaScript theme={null} const BASE = 'https://nano-gpt.com/api'; async function getVideoStatus(requestId, apiKey) { const url = new URL(`${BASE}/video/status`); url.searchParams.set('requestId', requestId); const res = await fetch(url, { headers: { 'x-api-key': apiKey } }); if (!res.ok) throw new Error(`Status check failed: ${res.status}`); return res.json(); } export async function waitForVideo(requestId, apiKey, maxAttempts = 120, delayMs = 5000) { for (let i = 0; i < maxAttempts; i++) { const payload = await getVideoStatus(requestId, apiKey); const status = payload?.data?.status; if (status === 'COMPLETED') return payload.data.output.video.url; if (status === 'FAILED') throw new Error(payload?.data?.error || 'Video generation failed'); await new Promise((r) => setTimeout(r, delayMs)); } throw new Error('Video generation timed out'); } ``` ```bash cURL theme={null} # Single status check curl -s "https://nano-gpt.com/api/video/status?requestId=vid_m1abc123def456" \ -H "x-api-key: YOUR_API_KEY" | jq . # Poll until complete (~10 minutes max) for i in {1..120}; do RESP=$(curl -s "https://nano-gpt.com/api/video/status?requestId=vid_m1abc123def456" -H "x-api-key: YOUR_API_KEY") STATUS=$(echo "$RESP" | jq -r '.data.status // empty') echo "Attempt $i: status=$STATUS" if [ "$STATUS" = "COMPLETED" ]; then echo "$RESP" | jq . VIDEO_URL=$(echo "$RESP" | jq -r '.data.output.video.url') echo "Video URL: $VIDEO_URL" break fi sleep 5 done ``` ## Status values * `IN_QUEUE`: Request queued * `IN_PROGRESS`: Generation in progress * `COMPLETED`: Video ready * `FAILED`: Generation failed * `CANCELED`: Request canceled ## Response examples ### In progress ```json theme={null} { "requestId": "vid_m1abc123def456", "model": "sora-2", "data": { "status": "IN_PROGRESS", "requestId": "vid_m1abc123def456" } } ``` ### Completed ```json theme={null} { "requestId": "vid_m1abc123def456", "model": "sora-2", "data": { "status": "COMPLETED", "requestId": "vid_m1abc123def456", "output": { "video": { "url": "https://storage.example.com/video.mp4" } }, "cost": 0.35 } } ``` ### Failed ```json theme={null} { "requestId": "vid_m1abc123def456", "model": "sora-2", "data": { "status": "FAILED", "requestId": "vid_m1abc123def456", "error": "Content policy violation", "isNSFWError": true, "userFriendlyError": "Content flagged as inappropriate. Please modify your prompt and try again." } } ``` Notes: * Terminal results are cached in `video_jobs` for faster subsequent status checks. # Voice Cloning Source: https://docs.nano-gpt.com/api-reference/endpoint/voice-cloning POST https://nano-gpt.com/api/voice-clone/minimax Clone voices from reference audio and reuse them with compatible TTS models ## Overview NanoGPT supports voice cloning so you can create reusable custom voices from short reference audio clips and then use them in text-to-speech (TTS). There are two voice-clone providers exposed via NanoGPT: * **MiniMax voice clone**: creates a reusable `customVoiceId` you can pass as `voice` when using compatible MiniMax Speech TTS models. * **Qwen voice clone (1.7B)**: generates a speaker embedding file URL that you can pass to Qwen 3 TTS as `speaker_voice_embedding_file_url`. Both flows are asynchronous: 1. Submit a clone job, receive a `runId` (HTTP 202). 2. Poll the status endpoint until `status: "completed"`. ## Authentication All voice clone endpoints support: * API key auth: `x-api-key: ` (or `Authorization: Bearer `) * Session auth (web app): browser cookies ## Endpoints | Provider | Submit | Status | | -------- | ------------------------------- | -------------------------------------- | | MiniMax | `POST /api/voice-clone/minimax` | `POST /api/voice-clone/minimax/status` | | Qwen | `POST /api/voice-clone/qwen` | `POST /api/voice-clone/qwen/status` | ## MiniMax Voice Clone ### Submit a Clone Job ``` POST /api/voice-clone/minimax ``` Supports: * `multipart/form-data` (upload an audio file) * `application/json` (provide `audioUrl`) **JSON request** ```json theme={null} { "audioUrl": "https://example.com/reference-audio.mp3", "customVoiceId": "MyVoice001", "voiceCloneModel": "speech-02-hd", "needNoiseReduction": false, "needVolumeNormalization": false, "accuracy": 0.7, "text": "Hello! This is a preview of my cloned voice." } ``` **Form fields** | Field | Type | Required | Notes | | ------------------------------------------------------- | ------- | ---------------------- | ------------------------------------------------- | | `audio` | file | Yes (if no `audioUrl`) | MP3, M4A, WAV | | `audioUrl` | string | Yes (if no `audio`) | Hosted audio URL | | `customVoiceId` / `custom_voice_id` | string | Yes | Must match `^[A-Za-z][A-Za-z0-9]{7,}$` | | `voiceCloneModel` / `model` | string | No | Example values: `speech-02-hd`, `speech-02-turbo` | | `needNoiseReduction` / `need_noise_reduction` | boolean | No | Default `false` | | `needVolumeNormalization` / `need_volume_normalization` | boolean | No | Default `false` | | `accuracy` | number | No | 0 to 1, default `0.7` | | `text` / `previewText` | string | No | Preview text | **Response (202)** ```json theme={null} { "status": "pending", "runId": "abc123-def456", "model": "MiniMax-Voice-Clone", "cost": 1.0, "paymentSource": "USD", "isApiRequest": true, "fileName": "reference.mp3", "fileSize": 245000 } ``` ### Poll Job Status ``` POST /api/voice-clone/minimax/status ``` **Request body** ```json theme={null} { "runId": "abc123-def456", "cost": 1.0, "paymentSource": "USD", "isApiRequest": true } ``` **Response (in progress)** ```json theme={null} { "status": "processing" } ``` **Response (completed)** ```json theme={null} { "status": "completed", "audioUrls": ["https://cdn.example.com/preview-audio.mp3"], "metadata": { "model": "MiniMax-Voice-Clone" } } ``` ## Qwen Voice Clone (1.7B) ### Submit a Clone Job ``` POST /api/voice-clone/qwen ``` Supports: * `multipart/form-data` (upload an audio file) * `application/json` (provide `audioUrl`) **JSON request** ```json theme={null} { "audioUrl": "https://example.com/reference-audio.mp3", "referenceText": "Optional transcript of the reference clip." } ``` **Form fields** | Field | Type | Required | Notes | | ---------------------------------- | ------ | ---------------------- | ----------------------- | | `audio` | file | Yes (if no `audioUrl`) | MP3, OGG, WAV, M4A, AAC | | `audioUrl` / `audio_url` | string | Yes (if no `audio`) | Hosted audio URL | | `referenceText` / `reference_text` | string | No | Optional transcript | **Response (202)** ```json theme={null} { "status": "pending", "runId": "vc_run_789", "model": "qwen-voice-clone", "cost": 0.25, "paymentSource": "USD", "isApiRequest": true, "fileName": "audio_file", "fileSize": 0 } ``` ### Poll Job Status ``` POST /api/voice-clone/qwen/status ``` **Request body** ```json theme={null} { "runId": "vc_run_789", "cost": 0.25, "paymentSource": "USD", "isApiRequest": true } ``` **Response headers** While the job is still processing, the response may include an `X-Poll-After` header indicating how many seconds to wait before polling again. **Response (completed)** ```json theme={null} { "status": "completed", "speakerEmbeddingUrl": "https://storage.example.com/speaker-embedding.safetensors", "metadata": { "model": "qwen-voice-clone" } } ``` ## Using Cloned Voices with TTS ### MiniMax cloned voice (`customVoiceId`) Use your `customVoiceId` as the normal `voice` on `POST /api/tts` with a compatible MiniMax Speech TTS model: ```json theme={null} { "text": "Text you want spoken in the cloned voice.", "voice": "MyVoice001", "model": "Minimax-Speech-02-HD", "speed": 1 } ``` ### Qwen cloned voice (`speakerEmbeddingUrl`) Use `speakerEmbeddingUrl` as `speaker_voice_embedding_file_url` on `POST /api/tts` with `Qwen-3-TTS-1.7B`: ```json theme={null} { "text": "Text you want spoken in the cloned voice.", "model": "Qwen-3-TTS-1.7B", "speaker_voice_embedding_file_url": "https://storage.example.com/speaker-embedding.safetensors", "reference_text": "Optional: transcript of the original reference audio.", "language": "Auto" } ``` ## Saving MiniMax Voice IDs (Web App) If you use the NanoGPT web app, you can save and list your MiniMax `customVoiceId` values. These endpoints are **session-authenticated only** (they do not support API key auth). ### List Saved Voice IDs ``` GET /api/user/voice-ids ``` **Response** ```json theme={null} { "voiceIds": ["MyVoice001", "MyVoice002"] } ``` ### Save a Voice ID ``` POST /api/user/voice-ids ``` **Request body** ```json theme={null} { "voiceId": "MyVoice001" } ``` **Response** ```json theme={null} { "success": true, "voiceIds": ["MyVoice001", "MyVoice002"] } ``` ## Voice Clone Storage and Retention Last verified: February 21, 2026. Retention depends on the provider behind each voice clone model: * `minimax-voice-clone` (WaveSpeed + MiniMax): New cloned voice IDs are temporary. If a cloned voice is not used in a real TTS synthesis call within 7 days (168 hours), it is deleted. If it is used at least once in TTS within that window, it is kept long-term. Preview generated during clone creation does not activate or persist the voice. * `qwen-voice-clone` (fal.ai): The returned speaker embedding file URL is hosted by fal. fal guarantees hosted generated files for at least 7 days, then they may be removed at any time. Download and store the embedding yourself immediately for long-term reuse. * `inworld-voice-clone` (Inworld Voice API, if enabled in your workspace): Inworld does not publish a fixed auto-delete window for cloned voices in public docs. Treat cloned voices as persistent until explicitly deleted from your workspace. Note: Inworld's Zero Data Retention mode explicitly does not apply to voice-cloning audio samples. ## How to Keep and Reuse Voice Clones ### MiniMax / WaveSpeed (`customVoiceId`) 1. Save the returned voice ID (`customVoiceId`; provider docs may also call this `voice_id`). 2. Run at least one real TTS synthesis with that voice ID within 7 days. 3. Reuse the same voice ID in later TTS requests. ### Qwen (`speakerEmbeddingUrl`) 1. Save the returned `speakerEmbeddingUrl` (`speaker_embedding_url` in some provider docs). 2. Download the embedding file right away. 3. Store it in your own durable storage (S3, R2, etc.). 4. Use your stored URL later as `speaker_voice_embedding_file_url`. Example: ```bash theme={null} curl -L "$SPEAKER_EMBEDDING_URL" -o my-voice.safetensors ``` ### Inworld (`voice_id`, if enabled) 1. Save the returned `voice_id`. 2. Reuse it directly for Inworld TTS. 3. If deleted from Inworld, it must be re-cloned. ## Can I Download the Clone if It Gets Deleted? * MiniMax / WaveSpeed: no portable voice embedding download is documented; keep the voice ID active by using it in time. * Qwen: yes, download the speaker embedding file from `speakerEmbeddingUrl` / `speaker_embedding_url`. * Inworld: no documented voice-embedding export endpoint; keep the `voice_id` and avoid accidental deletion. > **Warning:** Provider retention policies may change. This page reflects provider docs as of February 21, 2026. ### Provider Source Links * MiniMax voice cloning intro: [https://platform.minimax.io/docs/api-reference/voice-cloning-intro](https://platform.minimax.io/docs/api-reference/voice-cloning-intro) * MiniMax voice clone endpoint: [https://platform.minimax.io/docs/api-reference/voice-cloning-clone](https://platform.minimax.io/docs/api-reference/voice-cloning-clone) * MiniMax FAQ (voice ID validity, activation, preview behavior): [https://platform.minimax.io/docs/faq/about-apis](https://platform.minimax.io/docs/faq/about-apis) * WaveSpeed MiniMax voice clone persistence notes: [https://wavespeed.ai/docs/docs-api/minimax/minimax-voice-clone](https://wavespeed.ai/docs/docs-api/minimax/minimax-voice-clone) * fal FAQ (file retention): [https://fal-d8505a2e.mintlify.app/model-apis/faq](https://fal-d8505a2e.mintlify.app/model-apis/faq) * fal Queue API (`X-Fal-Object-Lifecycle-Preference`): [https://fal-d8505a2e.mintlify.app/model-apis/mndpoints/queue](https://fal-d8505a2e.mintlify.app/model-apis/mndpoints/queue) * Inworld clone voice API: [https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/clone-voice](https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/clone-voice) * Inworld list voices: [https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/list-voices](https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/list-voices) * Inworld delete voice: [https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/delete-voice](https://docs.inworld.ai/api-reference/voiceAPI/voiceservice/delete-voice) * Inworld zero data retention (voice cloning samples excluded): [https://docs.inworld.ai/docs/tts/zero-data-retention](https://docs.inworld.ai/docs/tts/zero-data-retention) ## Pricing Clone runs are charged as a flat per-run fee: * MiniMax voice clone: \$1.00 per run * Qwen voice clone (1.7B): \$0.25 per run The submit response includes `cost` and `paymentSource` for the run. ## Limitations * MiniMax and Qwen clone endpoints are asynchronous; clients must poll status until completion. * MiniMax `customVoiceId` must match `^[A-Za-z][A-Za-z0-9]{7,}$`. # Direct Web Search API Source: https://docs.nano-gpt.com/api-reference/endpoint/web-search POST https://nano-gpt.com/api/web Run direct web search requests with explicit query control, provider-specific options, and Sofya search, fetch, extract, and research operations ## Overview Use `POST /api/web` when you want direct control over search requests and output formatting. You can also call this tool through the unified [Data API](/api-reference/endpoint/data-api) at `POST /api/v1/data/web/search`. The Data API preserves this endpoint's request body, response body, billing, and provider behavior while adding discovery and dispatch metadata. For chat-first workflows, use `POST /api/v1/chat/completions` with model suffixes like `:online`, `:online/linkup`, or `:online/sofya`, including provider-specific suffixes such as `:online/exa-instant`, `:online/exa-deep-reasoning`, `:online/brave`, and `:online/valyu-web-deep`. See [Model Suffixes](/api-reference/miscellaneous/model-suffixes#web-search-suffixes). Sofya can do more than search on this endpoint. Set `provider: "sofya"` and choose the `search`, `fetch`, `extract`, or `research` operation. ## When to use which endpoint | Use case | Endpoint | | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | | You want the model to answer with web context in one call | `POST /api/v1/chat/completions` + `:online...` | | You want one discoverable endpoint family for data tools | `POST /api/v1/data/web/search` | | You need explicit control over `query`, `outputType`, domain/date filters, or structured schema output | `POST /api/web` | | You need OpenAI-native web search | `POST /api/v1/chat/completions` only | ## Authentication Either auth header is supported: Bearer `YOUR_API_KEY` `YOUR_API_KEY` ## Accountless x402 Payment For accountless payment, prefer the public Data API path: ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/web/search \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "query": "What happened in AI this week?", "provider": "linkup", "depth": "standard", "outputType": "sourcedAnswer" }' ``` To request an accountless x402 quote, send the API request without `Authorization` or `x-api-key`, and include `x-x402: true`. NanoGPT will return `402 Payment Required` with available payment options. This endpoint supports accountless x402 payments where listed by `GET /api/v1/x402/endpoints`, including Lightning L402 when advertised. See [Accountless x402 API Payments](/api-reference/miscellaneous/x402) for the full flow. If you receive `401 missing_api_key` immediately, check that the initial quote request includes `x-x402: true`. Without that header, NanoGPT does not enter the x402 quote flow. ## Request body Search or research query. Required for every provider's search operation and for Sofya research. Not used by Sofya fetch or extract. Search provider: `linkup`, `tavily`, `exa`, `kagi`, `perplexity`, `valyu`, `brave`, `sofya`, or `firecrawl`. `openai-native` is not allowed on `/api/web`. Sofya operation: `search`, `fetch`, `extract`, or `research`. Other providers support `search` only. Search depth. For Linkup: `standard` or `deep`. For Linkup, `depth: "standard"` is executed as Linkup `fast` under the hood. Output mode. Allowed values: `searchResults`, `sourcedAnswer`, `structured`. Required when `outputType` is `structured`. Pass a JSON schema string. Include image results. Earliest result date (`YYYY-MM-DD`). Latest result date (`YYYY-MM-DD`). Restrict results to these domains. Exclude these domains. Linkup supports `outputType: "searchResults"`, `"sourcedAnswer"`, and `"structured"`. Non-Linkup providers currently support only `outputType: "searchResults"`. ### Sofya operations Sofya Search returns extracted page content instead of snippets alone. The other operations let you fetch known URLs, extract requested information from one page, or produce a cited multi-source research report. #### Search Use `operation: "search"` or omit `operation`. * `query` (string, required) * `maxResults` (integer, 1-20; default 10) * `topic` (`general` or `news`) * `freshness` (`day`, `week`, `month`, `year`, or `YYYY-MM-DD:YYYY-MM-DD`) * `includeDomains` / `excludeDomains` (up to 10 strings each) #### Fetch Use `operation: "fetch"` with: * `urls` (array of 1-10 URL strings, required) * `includeRawHtml` or `include_raw_html` (boolean) #### Extract Use `operation: "extract"` with: * `url` (string, required) * `prompt` (string, required), describing what to extract #### Research Use `operation: "research"` with: * `query` (string, required) * `topic` (`general` or `news`) * `freshness` (`day`, `week`, `month`, `year`, or `YYYY-MM-DD:YYYY-MM-DD`) * `maxSources` or `max_sources` (integer, 5-30) ## Response shape Provider-formatted payload. Executed query. Resolved provider (for example, `linkup`). Resolved operation. This is `search` except for Sofya fetch, extract, or research requests. Resolved depth. Resolved output type. ISO-8601 timestamp. Request cost in USD. For `searchResults`, `data` is normally an array of normalized results. Sofya extract and research return an object, while Sofya fetch returns an array. For `sourcedAnswer` and `structured`, `data` is the provider response object. ### Example response ```json theme={null} { "data": "... provider-formatted payload ...", "metadata": { "query": "string", "provider": "linkup", "depth": "standard", "outputType": "sourcedAnswer", "timestamp": "ISO-8601", "cost": 0.006 } } ``` ## Pricing (hosted key) | Mode | Price | | --------------- | ----------------- | | Linkup standard | \$0.006 | | Linkup deep | \$0.06 | | Sofya search | \$0.01575 | | Sofya fetch | \$0.00525 per URL | | Sofya extract | \$0.02625 | | Sofya research | \$0.13125 | ## Error codes | HTTP status | Meaning | | ----------- | --------------------------------- | | `400` | Invalid parameters | | `401` | Invalid session or auth | | `402` | Insufficient balance or usage cap | | `429` | Rate limited | | `503` | Provider key missing | | `504` | Search failed or timed out | ## Examples ```bash cURL (Authorization) theme={null} curl -X POST https://nano-gpt.com/api/web \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What happened in AI this week?", "provider": "linkup", "depth": "standard", "outputType": "sourcedAnswer" }' ``` ```bash cURL (x-api-key) theme={null} curl -X POST https://nano-gpt.com/api/web \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Latest OpenAI announcements", "provider": "linkup", "depth": "deep", "outputType": "searchResults" }' ``` ```bash cURL (structured output) theme={null} curl -X POST https://nano-gpt.com/api/web \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Top 5 AI coding tools in 2026 with pricing", "provider": "linkup", "depth": "standard", "outputType": "structured", "structuredOutputSchema": "{\"type\":\"object\",\"properties\":{\"tools\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\"},\"price\":{\"type\":\"string\"}},\"required\":[\"name\"]}}},\"required\":[\"tools\"]}" }' ``` ```bash cURL (chat completions with Linkup) theme={null} curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o:online/linkup", "messages": [ { "role": "user", "content": "Summarize today'\''s top AI headlines." } ] }' ``` ```bash cURL (chat completions with Sofya) theme={null} curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o:online/sofya", "messages": [ { "role": "user", "content": "Summarize today'\''s top AI headlines." } ] }' ``` ### Sofya examples ```bash cURL (search) theme={null} curl -X POST https://nano-gpt.com/api/web \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "sofya", "operation": "search", "query": "NanoGPT API documentation", "maxResults": 10, "topic": "general" }' ``` ```bash cURL (fetch) theme={null} curl -X POST https://nano-gpt.com/api/web \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "sofya", "operation": "fetch", "urls": ["https://sofya.co/docs"], "includeRawHtml": false }' ``` ```bash cURL (extract) theme={null} curl -X POST https://nano-gpt.com/api/web \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "sofya", "operation": "extract", "url": "https://sofya.co/pricing", "prompt": "Extract the available plans and their prices." }' ``` ```bash cURL (research) theme={null} curl -X POST https://nano-gpt.com/api/web \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "sofya", "operation": "research", "query": "Compare current AI web search APIs", "maxSources": 15 }' ``` ## Related docs * [Chat Completion](/api-reference/endpoint/chat-completion) * [Data API](/api-reference/endpoint/data-api) * [Web Search in Text Generation Guide](/api-reference/text-generation) * [Model Suffixes](/api-reference/miscellaneous/model-suffixes) # YouTube Transcription Source: https://docs.nano-gpt.com/api-reference/endpoint/youtube-transcribe POST /youtube-transcribe Extract transcripts from YouTube videos programmatically. Supports multiple URLs per request and provides detailed response information including success/failure status for each video. ## Overview The YouTube Transcription API allows you to extract transcripts from YouTube videos programmatically. This is useful for content analysis, accessibility, research, or any application that needs to work with YouTube video content in text format. ## Authentication The API supports two authentication methods: ### 1. API Key Authentication (Recommended) Include your API key in the request headers: ``` x-api-key: YOUR_API_KEY ``` ### 2. Session Authentication If you're making requests from a browser with an active session, authentication will be handled automatically via cookies. ## Request Format ### Headers ```json theme={null} { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY" } ``` ### Body ```json theme={null} { "urls": [ "https://www.youtube.com/watch?v=VIDEO_ID_1", "https://youtu.be/VIDEO_ID_2", "https://youtube.com/watch?v=VIDEO_ID_3" ] } ``` ### Parameters | Parameter | Type | Required | Description | | --------- | --------- | -------- | ----------------------------------------------------------------- | | `urls` | string\[] | Yes | Array of YouTube URLs to transcribe. Maximum 10 URLs per request. | ### Supported YouTube URL Formats * `https://www.youtube.com/watch?v=VIDEO_ID` * `https://youtu.be/VIDEO_ID` * `https://youtube.com/embed/VIDEO_ID` * `https://m.youtube.com/watch?v=VIDEO_ID` * `https://youtube.com/live/VIDEO_ID` ## Response Format ### Success Response (200 OK) ```json theme={null} { "transcripts": [ { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "success": true, "title": "Rick Astley - Never Gonna Give You Up", "transcript": "We're no strangers to love\nYou know the rules and so do I..." }, { "url": "https://youtube.com/watch?v=invalid", "success": false, "error": "Video not found or transcripts not available" } ], "summary": { "requested": 2, "processed": 2, "successful": 1, "failed": 1, "totalCost": 0.01 } } ``` ### Response Fields #### `transcripts` Array Each transcript object contains: * `url` (string): The original YouTube URL * `success` (boolean): Whether the transcript was successfully retrieved * `title` (string, optional): Video title (only if successful) * `transcript` (string, optional): The full transcript text (only if successful) * `error` (string, optional): Error message (only if failed) #### `summary` Object * `requested`: Number of URLs provided in the request * `processed`: Number of valid YouTube URLs found and processed * `successful`: Number of transcripts successfully retrieved * `failed`: Number of transcripts that failed * `totalCost`: Total cost in USD for successful transcripts ### Error Responses #### 400 Bad Request ```json theme={null} { "error": "Please provide an array of YouTube URLs" } ``` #### 401 Unauthorized ```json theme={null} { "error": "Invalid session" } ``` #### 402 Payment Required ```json theme={null} { "error": "Insufficient balance. Current balance: $0.50, required: $1.00" } ``` #### 429 Too Many Requests ```json theme={null} { "error": "Rate limit exceeded. Please wait before sending another request." } ``` ## Pricing * **Cost**: \$0.01 USD per successful transcript * **Billing**: You are only charged for successfully retrieved transcripts * **Failed transcripts**: No charge ## Rate Limits * **10 requests per minute** per IP address * **10 URLs maximum** per request ## Code Examples ```javascript JavaScript/Node.js theme={null} const axios = require('axios'); async function getYouTubeTranscripts() { try { const response = await axios.post('https://nano-gpt.com/api/youtube-transcribe', { urls: [ 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'https://youtu.be/kJQP7kiw5Fk' ] }, { headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' } }); console.log('Transcripts:', response.data.transcripts); console.log('Summary:', response.data.summary); } catch (error) { console.error('Error:', error.response?.data || error.message); } } ``` ```python Python theme={null} import requests def get_youtube_transcripts(): url = 'https://nano-gpt.com/api/youtube-transcribe' headers = { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' } data = { 'urls': [ 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'https://youtu.be/kJQP7kiw5Fk' ] } try: response = requests.post(url, json=data, headers=headers) response.raise_for_status() result = response.json() print('Transcripts:', result['transcripts']) print('Summary:', result['summary']) except requests.exceptions.RequestException as e: print(f'Error: {e}') if hasattr(e.response, 'json'): print('Details:', e.response.json()) ``` ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/youtube-transcribe \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "urls": [ "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "https://youtu.be/kJQP7kiw5Fk" ] }' ``` ```php PHP theme={null} [ 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'https://youtu.be/kJQP7kiw5Fk' ] ]; $options = [ 'http' => [ 'header' => [ "Content-Type: application/json", "x-api-key: YOUR_API_KEY" ], 'method' => 'POST', 'content' => json_encode($data) ] ]; $context = stream_context_create($options); $result = file_get_contents($url, false, $context); if ($result === FALSE) { die('Error occurred'); } $response = json_decode($result, true); print_r($response); ?> ``` ## Best Practices 1. **Batch Requests**: Send multiple URLs in a single request (up to 10) rather than making individual requests for better efficiency. 2. **Error Handling**: Always check the `success` field for each transcript, as some videos may not have transcripts available. 3. **Rate Limiting**: Implement exponential backoff if you receive a 429 status code. 4. **URL Validation**: The API automatically detects and validates YouTube URLs, but validating on your end can save API calls. 5. **Cost Monitoring**: Use the `summary.totalCost` field to track your spending. ## Limitations 1. **Transcript Availability**: Not all YouTube videos have transcripts available. Videos may lack transcripts if: * The creator hasn't enabled auto-captions * The video is private or age-restricted * The video has been deleted * The video is a live stream without captions 2. **Language**: Transcripts are returned in their original language. The API doesn't provide translation services. 3. **Formatting**: Transcripts are returned as plain text with natural line breaks. Timestamp information is not included. ## Use Cases * **Content Analysis**: Analyze video content for keywords, topics, or sentiment * **Accessibility**: Create accessible versions of video content * **Research**: Study communication patterns, language use, or content trends * **SEO**: Extract content for search engine optimization * **Education**: Create study materials from educational videos * **Content Moderation**: Check video content for compliance ## Support For technical support or questions about the YouTube Transcription API: * Email: [support@nano-gpt.com](mailto:support@nano-gpt.com) * Documentation: [https://docs.nano-gpt.com](https://docs.nano-gpt.com) * Status Page: [https://status.nano-gpt.com](https://status.nano-gpt.com) # Evals and Observability Source: https://docs.nano-gpt.com/api-reference/evals Run prompt and model experiments, freeze datasets, version scorers, inspect traces, and aggregate eval metrics. # NanoGPT Evals and Observability API NanoGPT Evals lets you run durable prompt and model experiments, freeze datasets, version scorers, inspect traces, and aggregate latency, cost, token, error, and score trends. The API is available under `/api/v1/evals/*`. The same platform powers Prompt Lab at `/prompt-lab`. ## Core Concepts ### Projects A project groups datasets, experiments, traces, and dashboard metrics. If an experiment is created without a `project_id`, NanoGPT uses a default Prompt Lab project for the authenticated user. ### Datasets and Dataset Versions A dataset is an editable set of eval rows. A dataset version is a frozen snapshot of the dataset at a point in time. Experiments should use dataset versions when reproducibility matters. Dataset rows support: ```json theme={null} { "input": "User prompt or task", "expected_output": "Optional target answer", "context": "Optional extra context", "metadata": { "case": "optional structured metadata" } } ``` ### Scorers A scorer grades candidate outputs. Scorers are versioned, so an experiment stores the exact scorer snapshot used at run time. Supported scorer types: * `exact_match` * `contains` * `regex` * `json_schema` * `threshold` * `llm_judge` * `pairwise_llm` This version does not run arbitrary JavaScript or Python scorers. ### Experiments An experiment compares one or more candidates over a dataset or inline rows. Experiments are asynchronous and durable. Experiment statuses: * `queued` * `in_progress` * `completed` * `failed` * `cancelled` Experiments store candidate prompt, model, and config snapshots, scorer snapshots, progress, traces, scores, errors, and cost and usage metadata. ### Traces A trace records a generation or scoring call. Traces store metadata, timings, usage, cost, status, and errors. Prompt and output content is not stored unless explicitly requested. ### Privacy Defaults By default, traces are metadata-only. NanoGPT stores prompt and output content only when: * a user explicitly saves Prompt Lab dataset or experiment content * an API caller passes a content-storage opt-in such as `nanogpt_eval_store_content: true` Requested content storage can be suppressed when content is not safe or not available to store. When suppression happens, trace metadata includes `content_suppressed_reason`. Current suppression reasons include: | Reason | Description | | ---------------------------- | --------------------------------------------------------------------------- | | `pii_redaction_enabled` | Redaction was enabled for the request. | | `output_content_unavailable` | Content storage was requested, but no output text was available to persist. | ## Authentication Use the same authentication as the NanoGPT API. For API callers, pass your API key in the `Authorization` header: ```http theme={null} Authorization: Bearer $NANOGPT_API_KEY ``` All eval objects are scoped to the authenticated session, team, and API key context. ## Limits Current experiment limits: * up to 100 eval items per dataset or inline run * up to 5 candidates per experiment * up to 10 scorers per experiment * up to 100 generation and scoring work units per experiment Work units are calculated as: ```text theme={null} items * candidates * max(1, scorer_count + 1) ``` Eval run and item rate limits are applied to normal runs and reruns. Rate-limited responses return HTTP `429` with `Retry-After`. ## Object Shapes ### Project ```json theme={null} { "id": "project_...", "object": "eval.project", "name": "Support Bot", "description": "Support prompt evaluation", "settings": {}, "created_at": "2026-05-15T12:00:00.000Z", "updated_at": "2026-05-15T12:00:00.000Z" } ``` ### Dataset Version ```json theme={null} { "id": "datasetv_...", "object": "eval.dataset_version", "dataset_id": "evaldataset_...", "version": 3, "item_count": 25, "items": [ { "id": "evalitem_...", "dataset_item_id": "evalitem_...", "input": "Explain rate limits", "expected_output": "Mentions quotas and retry behavior", "context": null, "metadata": { "topic": "billing" }, "metadata_index": 0 } ], "source": "manual", "created_at": "2026-05-15T12:00:00.000Z" } ``` ### Scorer ```json theme={null} { "id": "scorer_...", "object": "eval.scorer", "version_id": "scorerv_...", "version": 1, "name": "Helpful Judge", "description": "Scores helpfulness from 0 to 1", "scorer_type": "llm_judge", "config": {}, "prompt": "Evaluate the response. Return {\"score\": number, \"reasoning\": string}.", "judge_model": "openai/gpt-5.4-mini", "created_at": "2026-05-15T12:00:00.000Z" } ``` ### Experiment ```json theme={null} { "id": "experiment_...", "object": "eval.experiment", "project_id": "project_...", "name": "Support answer comparison", "description": null, "dataset_version_id": "datasetv_...", "status": "in_progress", "progress": { "total_items": 10, "total_traces": 20, "completed_traces": 8, "failed_traces": 0, "total_scores": 40, "completed_scores": 12, "failed_scores": 0 }, "candidates": [], "scorers": [], "settings": { "store_content": true, "redaction": false }, "error": null, "created_at": "2026-05-15T12:00:00.000Z", "started_at": "2026-05-15T12:00:02.000Z", "completed_at": null, "cancelled_at": null, "expires_at": "2026-06-14T12:00:00.000Z" } ``` ### Trace ```json theme={null} { "id": "trace_...", "object": "eval.trace", "project_id": "project_...", "experiment_id": "experiment_...", "experiment_item_id": "experimentitem_...", "trace_type": "generation", "source": "prompt_lab_experiment", "group_id": "experiment_...", "parent_trace_id": null, "status": "completed", "model": "openai/gpt-5.4-mini", "provider": "nanogpt", "store_content": false, "input_content": null, "output_content": null, "metadata": { "candidate_id": "candidate_1" }, "usage": { "prompt_tokens": 120, "completion_tokens": 80 }, "cost_usd": 0.0004, "latency_ms": 1200, "error": null, "started_at": "2026-05-15T12:00:00.000Z", "completed_at": "2026-05-15T12:00:01.200Z", "expires_at": "2026-06-14T12:00:00.000Z" } ``` ## Quick Start ### 1. Create a dataset ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/evals/datasets" \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Support QA", "items": [ { "input": "Explain API rate limits to a non-technical founder.", "expected_output": "Mentions quotas, retry behavior, and practical next steps.", "metadata": { "topic": "api" } } ] }' ``` ### 2. Freeze a dataset version ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/evals/datasets/evaldataset_abc123/versions" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ### 3. Create a scorer ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/evals/scorers" \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Helpful Judge", "scorer_type": "llm_judge", "judge_model": "openai/gpt-5.4-mini", "prompt": "Evaluate whether the response is helpful. Input: {{input}}\nResponse: {{output}}\nExpected: {{expected_output}}\nReturn only JSON: {\"score\": number, \"reasoning\": string}." }' ``` ### 4. Create an async experiment ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/evals/experiments" \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Support QA prompt comparison", "dataset_version_id": "datasetv_abc123", "candidates": [ { "id": "baseline", "name": "Baseline", "model": "openai/gpt-5.4-mini", "system": "You are concise and practical.", "prompt": "{{input}}" }, { "id": "detailed", "name": "Detailed", "model": "openai/gpt-5.4-mini", "system": "You are clear, practical, and include examples.", "prompt": "{{input}}" } ], "scorer_ids": ["scorer_abc123"], "settings": { "store_content": true, "redaction": false } }' ``` The response returns an experiment with status `queued` or `in_progress`. Poll the experiment until status is terminal. ### 5. Poll the experiment ```bash theme={null} curl "https://nano-gpt.com/api/v1/evals/experiments/experiment_abc123" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ### 6. Read output items ```bash theme={null} curl "https://nano-gpt.com/api/v1/evals/experiments/experiment_abc123/output_items" \ -H "Authorization: Bearer $NANOGPT_API_KEY" ``` ## API Reference ### Projects #### List projects ```http theme={null} GET /api/v1/evals/projects ``` Returns: ```json theme={null} { "object": "list", "data": [] } ``` #### Create project ```http theme={null} POST /api/v1/evals/projects ``` Body: ```json theme={null} { "name": "Support Bot", "description": "Optional description", "settings": {} } ``` #### Get project ```http theme={null} GET /api/v1/evals/projects/{project_id} ``` #### Update project ```http theme={null} PATCH /api/v1/evals/projects/{project_id} ``` Body fields: ```json theme={null} { "name": "New name", "description": "New description", "settings": {} } ``` #### Delete project ```http theme={null} DELETE /api/v1/evals/projects/{project_id} ``` Returns: ```json theme={null} { "deleted": true, "id": "project_..." } ``` ### Datasets #### List datasets ```http theme={null} GET /api/v1/evals/datasets ``` #### Create dataset ```http theme={null} POST /api/v1/evals/datasets ``` Body: ```json theme={null} { "id": "evaldataset_optional_custom_id", "name": "Dataset name", "description": "Optional description", "items": [ { "input": "Required input", "output": "Optional existing output", "system": "Optional system message", "expected_output": "Optional expected output", "context": "Optional context", "metadata": {} } ] } ``` Custom dataset IDs must start with `evaldataset_` and contain 6 to 80 letters, numbers, underscores, or dashes after the prefix. #### Get dataset ```http theme={null} GET /api/v1/evals/datasets/{dataset_id} ``` Returns the dataset and its current items. #### Delete dataset ```http theme={null} DELETE /api/v1/evals/datasets/{dataset_id} ``` Deletes the dataset by marking it deleted. Historical runs and versions keep their snapshots. ### Dataset Versions #### List dataset versions ```http theme={null} GET /api/v1/evals/datasets/{dataset_id}/versions ``` #### Create dataset version ```http theme={null} POST /api/v1/evals/datasets/{dataset_id}/versions ``` Freezes the current dataset rows into a new immutable version. ### Scorers #### List scorers ```http theme={null} GET /api/v1/evals/scorers ``` Includes built-in scorers, legacy custom evaluators, and versioned scorers. #### Create scorer ```http theme={null} POST /api/v1/evals/scorers ``` Body: ```json theme={null} { "id": "optional_scorer_id", "scorer_id": "optional_scorer_id", "name": "Scorer name", "description": "Optional description", "scorer_type": "llm_judge", "config": {}, "prompt": "Required for llm_judge", "judge_model": "openai/gpt-5.4-mini" } ``` For `llm_judge`, `prompt` is required. If both `id` and `scorer_id` are omitted, NanoGPT generates a scorer ID. #### Get latest scorer ```http theme={null} GET /api/v1/evals/scorers/{scorer_id} ``` #### Delete scorer ```http theme={null} DELETE /api/v1/evals/scorers/{scorer_id} ``` Deletes all stored versions for the scorer ID. ## Scorer Configuration ### exact\_match Compares output to `expected_output`. Config: ```json theme={null} { "case_sensitive": false } ``` ### contains Checks whether output contains a configured value or `expected_output`. Config: ```json theme={null} { "value": "required substring", "case_sensitive": false } ``` ### regex Checks whether output matches a regular expression. Config: ```json theme={null} { "pattern": "success|passed", "flags": "i" } ``` If `pattern` is omitted, the scorer uses `expected_output` as the pattern. ### json\_schema Parses output as JSON and validates a supported JSON-schema subset. Config: ```json theme={null} { "schema": { "type": "object", "required": ["answer"], "properties": { "answer": { "type": "string", "minLength": 2 } } } } ``` Supported schema fields: * `type` * `required` * `properties` * `items` * `enum` * `minimum` * `maximum` * `minLength` * `maxLength` Nested properties and items validation is capped at 10 levels. ### threshold Converts a value to a number and passes if it is greater than or equal to a threshold. Config: ```json theme={null} { "source": "metadata.score", "threshold": 0.7 } ``` Supported sources: * `output` * `expected_output` * `metadata.score` ### llm\_judge Calls a judge model and expects JSON: ```json theme={null} { "score": 0.8, "reasoning": "The response directly answers the user." } ``` The score is clamped to `0..1`. Prompt templates may reference: * `{{input}}` * `{{output}}` * `{{expected_output}}` * `{{context}}` * `{{system}}` * `{{metadata.some_key}}` ### pairwise\_llm Compares a challenger candidate against a baseline candidate with an LLM judge. A score of `1` means the challenger is better, `0` means the baseline is better, and `0.5` means a tie. Config: ```json theme={null} { "baseline_candidate_id": "baseline" } ``` If no baseline is configured, the first candidate is used. ## Experiments ### List experiments ```http theme={null} GET /api/v1/evals/experiments ``` Query parameters: | Parameter | Description | | ------------ | ---------------------------- | | `project_id` | Optional project filter. | | `limit` | Default `50`, maximum `100`. | ### Create experiment ```http theme={null} POST /api/v1/evals/experiments ``` Body: ```json theme={null} { "project_id": "project_...", "name": "Experiment name", "description": "Optional description", "dataset_id": "evaldataset_...", "dataset_version_id": "datasetv_...", "data": [ { "input": "Inline row", "expected_output": "Optional expected output", "context": "Optional context", "metadata": {} } ], "candidates": [ { "id": "baseline", "name": "Baseline", "model": "openai/gpt-5.4-mini", "system": "Optional system message", "prompt": "{{input}}", "config": { "temperature": 0 } } ], "scorer_ids": ["scorer_..."], "settings": { "store_content": true, "redaction": false, "trace_group": "optional-group", "judge_model": "openai/gpt-5.4-mini" } } ``` Use one data source: * `dataset_id` * `dataset_version_id` * inline `data` or `items` Do not pass both `dataset_id` and `dataset_version_id`. For inline experiments, content storage must be enabled because the experiment needs row snapshots to run asynchronously. Candidate fields: | Field | Description | | -------- | -------------------------------------------------------------------------------------- | | `id` | Optional. Defaults to `candidate_1`, `candidate_2`, and so on. | | `name` | Optional candidate name. | | `model` | Required model ID. | | `system` | Optional system message. | | `prompt` | Optional. Defaults to `{{input}}`. | | `config` | Optional generation config. `model`, `messages`, and `stream` are ignored if included. | The endpoint returns `202 Accepted` and an experiment object. ### Get experiment ```http theme={null} GET /api/v1/evals/experiments/{experiment_id} ``` Use this endpoint to poll status and progress. ### Cancel experiment ```http theme={null} POST /api/v1/evals/experiments/{experiment_id}/cancel ``` Only queued or in-progress experiments can be cancelled. ### Rerun experiment ```http theme={null} POST /api/v1/evals/experiments/{experiment_id}/rerun ``` Creates a new experiment from the original experiment snapshot and schedules it asynchronously. ### List experiment output items ```http theme={null} GET /api/v1/evals/experiments/{experiment_id}/output_items ``` Query parameters: | Parameter | Description | | ---------------- | ---------------------------------------------------------------------- | | `redact_content` | Set to `true` to hide stored input and output content in the response. | Response: ```json theme={null} { "experiment": {}, "items": [], "data": [ { "id": "trace_...", "object": "eval.trace", "status": "completed", "output_content": "Stored output if store_content was true", "scores": [ { "id": "score_...", "scorer_id": "scorer_...", "score": 0.8, "reasoning": "Good answer", "status": "completed" } ] } ] } ``` ## Traces ### List traces ```http theme={null} GET /api/v1/evals/traces ``` Query parameters: | Parameter | Description | | --------------- | ---------------------------------------------------------------------------------------------------------------- | | `project_id` | Optional project filter. | | `experiment_id` | Optional experiment filter. | | `model` | Optional model filter. | | `provider` | Optional routing label. Public responses use generic NanoGPT routing labels rather than internal provider names. | | `status` | Optional status filter. | | `source` | Optional source filter. | | `limit` | Default `50`, maximum `200`. | ### Get trace ```http theme={null} GET /api/v1/evals/traces/{trace_id} ``` Returns the trace plus attached scores. ## Dashboard ```http theme={null} GET /api/v1/evals/dashboard ``` Query parameters: | Parameter | Description | | --------------- | --------------------------- | | `project_id` | Optional project filter. | | `experiment_id` | Optional experiment filter. | Returns aggregate metrics: ```json theme={null} { "trace_count": 100, "cost_usd": 0.42, "prompt_tokens": 10000, "completion_tokens": 5000, "avg_latency_ms": 1200, "p50_latency_ms": 900, "p95_latency_ms": 2400, "error_count": 2, "error_rate": 0.02, "model_provider_breakdown": [], "scorer_trends": [] } ``` ## Opt-in Chat Completion Tracing Normal `/v1/chat/completions` requests do not create eval traces. To trace a normal API request, add `metadata.nanogpt_eval_trace: true` to the chat completion request. Example: ```bash theme={null} curl -X POST "https://nano-gpt.com/v1/chat/completions" \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.4-mini", "messages": [ { "role": "user", "content": "Explain API rate limits." } ], "metadata": { "nanogpt_eval_trace": true, "nanogpt_eval_project_id": "project_...", "nanogpt_eval_trace_group": "docs-example", "nanogpt_eval_store_content": false, "customer_request_id": "kept-and-forwarded" } }' ``` Supported eval metadata keys: | Key | Description | | ---------------------------- | ----------------------------------------------------------- | | `nanogpt_eval_trace` | Boolean. Must be `true` to create a trace. | | `nanogpt_eval_project_id` | Optional project ID. | | `nanogpt_eval_experiment_id` | Optional experiment ID. | | `nanogpt_eval_trace_group` | Optional group ID. | | `nanogpt_eval_store_content` | Boolean. Stores prompt and output content only when `true`. | NanoGPT strips only `metadata.nanogpt_eval_*` keys before provider dispatch. Other metadata keys remain untouched. If `nanogpt_eval_store_content` is omitted or `false`, the trace stores metadata, usage, cost, latency, status, and errors, but not prompt or output content. If `nanogpt_eval_store_content` is `true` but the request does not produce output text available to the trace recorder, NanoGPT keeps the trace metadata-only and records `content_suppressed_reason: "output_content_unavailable"` in trace metadata. ## Legacy Evaluator Endpoints The original evaluator API remains available for compatibility. ### List legacy evaluators ```http theme={null} GET /api/v1/evals ``` ### Create legacy evaluator ```http theme={null} POST /api/v1/evals ``` Body: ```json theme={null} { "id": "eval_optional_custom_id", "name": "Helpfulness", "description": "Optional", "prompt": "Evaluate this response. Return JSON with score and reasoning.", "judge_model": "openai/gpt-5.4-mini" } ``` Custom evaluator IDs must start with `eval_` and contain 6 to 80 letters, numbers, underscores, or dashes after the prefix. ### Run legacy evaluator ```http theme={null} POST /api/v1/evals/{eval_id}/runs ``` Body: ```json theme={null} { "dataset_id": "evaldataset_...", "data": [ { "input": "Question", "output": "Candidate answer", "expected_output": "Expected answer", "context": "Optional context", "metadata": {} } ], "store": true, "judge_model": "openai/gpt-5.4-mini", "redaction": false, "concurrency": 4, "metadata": {} } ``` Use either `dataset_id` or inline `data`. ### Get stored legacy run ```http theme={null} GET /api/v1/evals/{eval_id}/runs/{run_id} ``` ### Get stored legacy run output items ```http theme={null} GET /api/v1/evals/{eval_id}/runs/{run_id}/output_items ``` ## Retention Default retention is 30 days. Trace records, stored trace content, jobs, and old experiment artifacts are cleaned up by the eval cleanup job. Projects, datasets, dataset versions, and scorers are durable until deleted. ## Error Responses Validation errors return HTTP `400`: ```json theme={null} { "error": "name is required" } ``` Missing resources return HTTP `404`: ```json theme={null} { "error": "Experiment not found" } ``` Rate limits return HTTP `429`: ```json theme={null} { "error": { "message": "Too many eval runs. Please slow down and try again later.", "type": "rate_limit_error", "code": "rate_limit_exceeded" } } ``` Unexpected server failures return HTTP `500`: ```json theme={null} { "error": "Internal Server Error" } ``` ## Operational Notes The eval platform stores durable project, dataset, scorer, experiment, trace, and score records in dedicated tables, including: * `eval_projects` * `eval_dataset_versions` * `eval_scorer_versions` * `eval_experiments` * `eval_experiment_items` * `eval_traces` * `eval_trace_scores` The migration also ensures the legacy eval tables exist. Async experiment execution uses NanoGPT's background scheduler. Stale queued or in-progress experiments are retried, and the cleanup job removes expired legacy runs, traces, and experiments. # Image API Source: https://docs.nano-gpt.com/api-reference/image-generation Discover image models, inspect endpoint metadata and pricing, and generate images through NanoGPT's normalized Image API. ## Overview NanoGPT provides a dedicated Image API for discovering image models, inspecting supported parameters and pricing, and generating images through a normalized endpoint. Use this API when you want one image workflow: * Discover available image models with machine-readable capabilities. * Inspect a model's public endpoint metadata, supported parameters, pricing, and image-input constraints. * Generate text-to-image and image-to-image outputs through `POST /api/v1/images`. Existing image routes remain supported for compatibility: * `POST /api/v1/images/generations` * `POST /api/v1/images/edits` * `POST /api/v1/images/edit` * `GET /api/v1/image-models` The dedicated Image API is additive. New integrations should prefer the normalized routes below. ## Authentication Use the same NanoGPT API key authentication as other API routes: ```http theme={null} Authorization: Bearer YOUR_API_KEY ``` or: ```http theme={null} x-api-key: YOUR_API_KEY ``` Model discovery routes can be called without authentication. Generation requests require an API key. ## Quickstart ```bash theme={null} curl https://nano-gpt.com/api/v1/images/models ``` ```bash theme={null} curl https://nano-gpt.com/api/v1/images/models/gpt-image-2/endpoints ``` ```bash theme={null} curl https://nano-gpt.com/api/v1/images \ -H "Content-Type: application/json" \ -H "x-api-key: $NANOGPT_API_KEY" \ -d '{ "model": "gpt-image-2", "prompt": "A clean product photo of a matte black espresso machine", "resolution": "1024x1024", "quality": "medium", "n": 1 }' ``` ## Discover Image Models List image models: ```http theme={null} GET /api/v1/images/models ``` Response: ```json theme={null} { "object": "list", "data": [ { "id": "gpt-image-2", "name": "GPT Image 2", "description": "Model description...", "created": 1760000000, "owned_by": "openai", "architecture": { "input_modalities": ["text", "image"], "output_modalities": ["image"] }, "supported_parameters": { "resolution": { "type": "enum", "values": ["1024x1024", "1024x768"], "default": "1024x1024" }, "n": { "type": "range", "min": 1, "max": 4, "default": 1 }, "quality": { "type": "enum", "values": ["low", "medium", "high"], "default": "medium" }, "input_references": { "type": "range", "min": 0, "max": 4 } }, "supports_streaming": false, "endpoints": "/api/v1/images/models/gpt-image-2/endpoints", "capabilities": { "image_generation": true, "image_to_image": true, "inpainting": false, "nsfw": false }, "category": "image" } ], "meta": { "count": 201, "generated_at": "2026-06-25T00:00:00.000Z" } } ``` Notes: * `id` is the model ID to use in `POST /api/v1/images`. * `endpoints` is the URL for endpoint and pricing metadata for that model. * `supported_parameters` is machine-readable and varies by model. * `supports_streaming` is currently always `false`. * Model IDs may contain slashes. Prefer using the returned `endpoints` URL directly. See [List Image Models](/api-reference/endpoint/image-api-models) for the endpoint reference. ## Inspect Endpoint Metadata And Pricing Get public endpoint metadata for a model: ```http theme={null} GET /api/v1/images/models/{modelId}/endpoints ``` Example: ```http theme={null} GET /api/v1/images/models/gpt-image-2/endpoints ``` For model IDs that contain slashes, use the `endpoints` path returned by `GET /api/v1/images/models`. Response: ```json theme={null} { "id": "gpt-image-2", "endpoints": [ { "provider_name": "OpenAI", "provider_slug": "openai", "provider_tag": null, "supported_parameters": { "resolution": { "type": "enum", "values": ["1024x1024", "1024x768"], "default": "1024x1024" }, "n": { "type": "range", "min": 1, "max": 4, "default": 1 } }, "allowed_passthrough_parameters": [], "supports_streaming": false, "pricing": [ { "billable": "output_image", "unit": "image", "cost_usd": 0.06551, "resolution": "1024x1024" } ], "input_reference_constraints": { "max_items": 4, "route": { "min_width": 8, "min_height": 8, "max_bytes": 31457280, "formats": ["png", "jpeg", "webp"], "source": "route-preflight" } } } ] } ``` Notes: * NanoGPT currently returns one public endpoint per model. * `provider_tag` is `null` because provider selection is not exposed yet. * `allowed_passthrough_parameters` is an empty array for now. * `pricing` uses public NanoGPT pricing, not provider at-cost rates. * `input_reference_constraints` is included when the model supports image inputs. See [Get Image Model Endpoints](/api-reference/endpoint/image-api-model-endpoints) for the endpoint reference. ## Generate Images Generate images with: ```http theme={null} POST /api/v1/images Content-Type: application/json ``` Text-to-image request: ```json theme={null} { "model": "gpt-image-2", "prompt": "A clean product photo of a matte black espresso machine on a white counter", "resolution": "1024x1024", "quality": "medium", "n": 1 } ``` Image-to-image request: ```json theme={null} { "model": "gpt-image-2", "prompt": "Make this look like a polished studio product photo", "input_references": [ { "type": "image_url", "image_url": { "url": "https://example.com/reference.png" } } ], "resolution": "1024x1024", "quality": "medium", "n": 1 } ``` Use `n` to request the number of output images. Internally, `n` is normalized to NanoGPT's existing `nImages` parameter. If both are supplied, `nImages` takes precedence. See [Generate Images](/api-reference/endpoint/image-api-generate) for the endpoint reference. ## Image References `input_references` accepts an array of image references. Supported entries: ```json theme={null} [ "https://example.com/image.png", "data:image/png;base64,...", { "type": "image_url", "image_url": { "url": "https://example.com/image.png" } } ] ``` Do not mix `input_references` with legacy image aliases such as `imageDataUrl`, `imageDataUrls`, `image_url`, or `images` in the same request. The route returns `conflicting_image_inputs` if both styles are supplied. ## Supported Parameter Discovery Parameter support varies by model. Always check `supported_parameters` from: ```http theme={null} GET /api/v1/images/models ``` Common fields include: * `model` * `prompt` * `n` * `resolution` * `aspect_ratio` * `quality` * `output_format` * `seed` * `input_references` NanoGPT exposes many model-specific parameters. Treat them as discoverable via `supported_parameters`, not as globally supported fields. `seed` is an optional model-specific hint that may improve reproducibility where supported by the model/provider route. Identical results are not guaranteed. Check the selected model's `supported_parameters` before using it. Any stronger reproducibility guarantee belongs in documentation for the specific model/provider route, not this generic endpoint guide. ## Unsupported Features For Now ### Streaming `stream: true` is not supported yet. ```json theme={null} { "error": { "message": "stream is not supported for /api/v1/images yet. Use non-streaming requests or /api/v1/images/generations.", "type": "invalid_request_error", "code": "unsupported_stream", "parameter": "stream" }, "code": "unsupported_stream" } ``` ### Provider Selection Provider selection and provider passthrough are not supported yet on `POST /api/v1/images`. Do not send provider routing or provider passthrough options: ```json theme={null} { "provider": { "only": ["openai"] } } ``` Non-empty provider objects return: ```json theme={null} { "error": { "message": "Provider selection and provider passthrough options are not supported for /api/v1/images yet.", "type": "invalid_request_error", "code": "unsupported_provider_options", "parameter": "provider" }, "code": "unsupported_provider_options" } ``` Empty provider objects are ignored, but clients should not rely on that behavior. ## Migration Notes Use the dedicated Image API when building new image integrations. It provides model discovery, endpoint metadata, public pricing metadata, image-input constraints, and normalized JSON generation in one workflow. Use the legacy routes when you need their existing compatibility behavior: * Use `POST /api/v1/images/generations` or `POST /v1/images/generations` for OpenAI-compatible image generation request shapes. * Use `POST /api/v1/images/edits` or `POST /api/v1/images/edit` for OpenAI-compatible image editing. * Use `GET /api/v1/image-models` if your integration already depends on the older image model list format. Do not assume field names are interchangeable between the normalized Image API and legacy endpoints. For example, `POST /api/v1/images` uses `input_references`; older routes support aliases such as `imageDataUrl` and `imageDataUrls`. ## Error Reference ### Missing Model ```json theme={null} { "error": { "message": "model is required for /api/v1/images.", "type": "invalid_request_error", "code": "missing_model", "parameter": "model" }, "code": "missing_model" } ``` ### Invalid Content Type `POST /api/v1/images` accepts JSON only. ```json theme={null} { "error": { "message": "/api/v1/images accepts application/json requests.", "type": "invalid_request_error", "code": "invalid_content_type" }, "code": "invalid_content_type" } ``` ### Invalid Input References ```json theme={null} { "error": { "message": "input_references must be an array of image URL strings or image_url objects.", "type": "invalid_request_error", "code": "invalid_input_references", "parameter": "input_references" }, "code": "invalid_input_references" } ``` ### Conflicting Image Inputs ```json theme={null} { "error": { "message": "Use input_references or image input aliases, not both.", "type": "invalid_request_error", "code": "conflicting_image_inputs", "parameter": "input_references" }, "code": "conflicting_image_inputs" } ``` # Advisor Source: https://docs.nano-gpt.com/api-reference/miscellaneous/advisor Let one model consult a different second model before producing its final answer. ## Overview Advisor lets the model handling a [Chat Completion](/api-reference/endpoint/chat-completion) or [Responses](/api-reference/endpoint/responses) request consult one client-selected second model before returning its final answer. * The top-level `model` is the executor. * `advisor.model` is the model it may consult. * In `auto` mode, the executor decides whether advice is useful. * In `required` mode, NanoGPT requires one valid consultation attempt before the executor can return its final answer. * The executor and advisor must resolve to different model IDs. Advisor is a NanoGPT request extension. It is not a model ID and does not use OpenRouter's `openrouter:advisor` tool shape. ## Chat Completions example ```bash theme={null} curl https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [ { "role": "user", "content": "Review this database migration plan and identify the riskiest assumption." } ], "stream": false, "advisor": { "model": "anthropic/claude-opus-4.8", "mode": "auto" } }' ``` The returned object remains a normal non-streaming Chat Completions response from the executor, with additional top-level `advisor` metadata. ## Responses API example ```bash theme={null} curl https://nano-gpt.com/api/v1/responses \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "input": "Review this database migration plan and identify the riskiest assumption.", "stream": false, "advisor": { "model": "anthropic/claude-opus-4.8", "mode": "required" } }' ``` The returned object remains a native non-streaming Responses API object. Responses Advisor supports normal response storage and conversation history, including `store`, `previous_response_id`, and `conversation`. ## Advisor request object | Field | Type | Required | Behavior | | ----------------------- | ------- | -------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `model` | string | Yes | The single model the executor may consult. It must resolve differently from the executor model. Model arrays and automatic advisor-model selection are not supported. | | `instructions` | string | No | Additional instructions for the advisor, limited to 16,000 characters. The executor still supplies the focused consultation question. | | `mode` | string | No | `"auto"` is the default and lets the executor decide whether to consult. `"required"` requires one valid consultation attempt before the final answer. | | `max_uses` | integer | No | Only `1` is accepted, and `1` is the default. | | `max_completion_tokens` | integer | No | Optional maximum advisor output tokens. When omitted, NanoGPT does not impose an Advisor-specific output cap and the selected model/provider defaults apply. When supplied, it must be a positive integer. | | `forward_transcript` | boolean | No | Only `false` is accepted, and `false` is the default. The original conversation is not automatically copied into the advisor request. | Unknown Advisor fields are rejected. Top-level `provider` and `X-Provider` routing controls apply to the executor's initial and continuation calls. The advisor model uses its own normal routing. Use a provider-specific advisor model ID or supported routing suffix if provider control is required for that phase. See [Provider Selection](/api-reference/miscellaneous/provider-selection). ## Mode semantics ### Auto `mode: "auto"` allows the executor to answer directly or make one consultation. If it answers directly, there is no advisor or continuation call. ### Required `mode: "required"` explicitly forces the internal consultation tool choice. If the executor provider ignores that requirement or returns a missing or malformed consultation request, NanoGPT fails closed instead of presenting the result as consulted. Required mode guarantees a valid consultation attempt, not successful advice. After the executor makes a valid consultation request, an advisor-provider failure can still be reported to the executor so it can produce a final continuation marked with failed Advisor metadata. Relevant error codes include: * `advisor_required_consultation_missing` * `advisor_required_consultation_invalid` * `advisor_phase_timeout` * `advisor_failed` See [Error Handling](/api-reference/miscellaneous/error-handling) for the standard error envelope and retry guidance. ## Billing Advisor is initially available only to platform-billed, pay-as-you-go API-key requests. * If no consultation occurs in `auto` mode, only the executor call is billed normally. * If consultation occurs, the initial executor turn, advisor turn, and final executor continuation are separate model calls. * Every completed model call is billed at its applicable platform model rate. * Completed earlier phases remain billable if a later phase fails. * Advisor therefore usually increases both total cost and latency when consultation occurs. Advisor is not a fixed-price add-on. There is no separate flat Advisor charge; cost comes from the individual model calls. See [Pricing](/api-reference/miscellaneous/pricing). ## Privacy `forward_transcript: false` means NanoGPT does not automatically copy the original conversation into the advisor request. It does not mean the advisor receives no information from the conversation. The executor creates the focused consultation question and can include details it considers necessary. That question is sent to the selected advisor model's provider. Choose an advisor model and provider whose data-handling policy is acceptable for the request. Saved memory is disabled on every internal call. Requests that include memory or server-side content enhancements are rejected instead of silently changing their behavior. Advisor is not available through Private Mode. Enabling Advisor on the standard API does not make either model call Private/TEE. ## Initial limitations Advisor requests must: * Use `POST /api/v1/chat/completions` or `POST /api/v1/responses`. * Be non-streaming (`stream` omitted or `false`). * Use an executor model with function tool-calling support. * Select one explicit advisor model that resolves differently from the executor. * Use a platform-billed pay-as-you-go API key. Advisor requests cannot currently use: * Client `tools`, `tool_choice`, `parallel_tool_calls`, legacy `functions`/`function_call`, or `max_tool_calls`, even when empty or disabled. * Structured output through Chat Completions `response_format`/`structured_outputs` or a non-text Responses `text.format`. * Fusion. * Context Memory or memory model suffixes. * Server-side web search, URL scraping, or YouTube transcript enhancements. * BYOK headers or body configuration. * Paid inline moderation headers. * Subscriptions. * Accountless x402 or L402 payment. * Private Mode. * Responses `background: true`. Conflicting requests return a `400` error rather than silently dropping the unsupported option. ## Response metadata Successful responses contain a top-level `advisor` object with fields including: * `id` * `mode` * `executor_model` * `advisor_model` * `requested` * `consulted` * `successful` * `consultation_count` * `max_uses` * `status` * Per-phase `usage` * Per-phase `pricing` when available Status meanings: * `not_used`: an `auto` executor answered without consultation. * `completed`: consultation returned usable advice and the executor produced its continuation. * `failed`: consultation was attempted but did not return usable advice; the executor could still produce a final continuation. `requested` means the executor emitted the internal consultation request. `consulted` means the advisor child call was attempted, while `successful` records whether it returned usable advice. Top-level numeric `usage` and `x_nanogpt_pricing` aggregate billable phases that returned those fields. Per-phase reconciliation data is available under `advisor.usage` and `advisor.pricing`, using `executor`, `advisor`, `continuation`, and `total` breakdowns. If a billable phase does not expose pricing metadata, NanoGPT omits aggregate pricing instead of showing a misleading partial total. Standard NanoGPT responses also include `x-nanogpt-advisor-id` for billing and support correlation. Whitelabel responses intentionally omit this NanoGPT-branded header and should use `X-Request-ID` for support correlation. ## Timeouts and failure behavior Each internal model phase has a 10-minute deadline. * An advisor-phase timeout fails open after a valid consultation request and gives the executor a tool result explaining that advice was unavailable. * An initial-executor or final-continuation timeout terminates the request with `advisor_phase_timeout`. * A failed consultation is not retried automatically. ## TypeScript SDK The first-party TypeScript SDK exposes Advisor request and response types on both Chat Completions and Responses request helpers. The standard OpenAI SDK can still call Advisor by passing the NanoGPT extension through its extra-body or custom-typing escape hatch, but upstream OpenAI SDK types do not know about the NanoGPT-specific `advisor` field. # Auto Recharge Source: https://docs.nano-gpt.com/api-reference/miscellaneous/auto-recharge Information about automatically recharging your account # Auto Recharge It's possible to turn on auto-recharge so that you can be sure not to run out of funds while using our API. This will automatically top-up your account from your credit card when your balance falls beneath a minimum that you specify. Note that you must have made a payment or make a payment first to be able to enable auto recharge. Payment details are stored by our payment provider (Stripe) only, we do not store these on our own servers. You can request deletion of your personal information directly from Stripe by visiting their [data deletion request page](support.stripe.com/questions/i-would-like-to-delete-the-information-stripe-has-collected-from-me). We detect low balances every 5 minutes, so make sure to leave some buffer in your minimum before a recharge happens. Minimum auto-recharge is \$10. # Pay-As-You-Go Billing Override Source: https://docs.nano-gpt.com/api-reference/miscellaneous/billing-override Force pay-as-you-go billing for subscription-included models # Pay-As-You-Go Billing Override (API) This override forces a request to be treated as paid (pay-as-you-go), even when the user has an active subscription and the model is subscription-included. ## What It Does * Forces a request to be treated as paid (pay-as-you-go). * Bypasses subscription coverage for that request. * Allows saved provider preferences to apply to subscription-included traffic. * Does not change which models support provider selection. ## When to Use It Use `billing_mode: "paygo"` when: * A subscriber wants to force pay-as-you-go billing without selecting a provider in the request. * A subscriber wants saved provider preferences to apply to subscription-included traffic. Explicit per-request provider selection already uses pay-as-you-go billing. If a subscriber sends `X-Provider` for a subscription-included model, the request is charged at the selected provider price and does not need `X-Billing-Mode: paygo`. ## Supported Inputs **Header** * `X-Billing-Mode: paygo` (header name is case-insensitive) * `x-billing-mode` is also accepted **Body (all endpoints)** * `"billing_mode": "paygo"` * Alias: `"billingMode": "paygo"` **Accepted values (case-insensitive)** * `paygo` * `pay-as-you-go` * `pay_as_you_go` * `paid` * `payg` **Precedence**: Body value wins if both body and header are present. ## API Examples Example with explicit provider selection: ```http theme={null} POST /api/v1/chat/completions X-Provider: deepinfra Content-Type: application/json { "model": "openai/gpt-oss-20b", "messages": [ { "role": "user", "content": "Say hello in one sentence." } ] } ``` Example forcing paygo without explicit provider selection: ```http theme={null} POST /api/v1/chat/completions X-Billing-Mode: paygo Content-Type: application/json { "model": "openai/gpt-oss-20b", "messages": [ { "role": "user", "content": "Say hello in one sentence." } ] } ``` Responses: ```http theme={null} POST /api/v1/responses X-Billing-Mode: paygo { "model": "gpt-4o-mini", "input": "Hello" } ``` ## Provider Selection Notes * Provider selection only works for models that support it. * If a model does not support provider selection, `X-Provider` is ignored. * `X-Provider` explicitly selects a provider for the request. * Explicit provider selection is always pay-as-you-go and is charged at the selected provider's price, including provider-selection markup. * For subscription users, sending `X-Provider` bypasses subscription coverage for that request. You do not need to also send `X-Billing-Mode: paygo`. * You cannot currently force a provider and have that same request count as subscription-included usage. ## Subscription and Limits Behavior When `billing_mode: "paygo"` is set: * The request is not eligible for subscription coverage. * Subscription quota is not consumed. * Pay-as-you-go balance checks apply as normal. If `subscription.enablePaidModelsApi` is `false`, the request is rejected (same as any other paid API request). ## FAQ **Does this affect which models are available?** No. It only changes billing eligibility. Model availability is unchanged. **Does it force a provider for non-selectable models?** No. It only affects billing. Use `X-Provider` to explicitly select a provider for models that support provider selection. **Can I combine this with BYOK?** Yes. BYOK still works; paygo override only changes subscription eligibility. # Brave Source: https://docs.nano-gpt.com/api-reference/miscellaneous/brave Brave provider notes # Brave Brave is supported as a standard web search provider in NanoGPT. For primary usage, use the main web search docs: * [Chat Completion web search](/api-reference/endpoint/chat-completion) * [Direct Web Search API (`POST /api/web`)](/api-reference/endpoint/web-search) * [Web Search in Text Generation Guide](/api-reference/text-generation) ## Common usage * `webSearch.provider: "brave"` * Model suffixes: `:online/brave` and `:online/brave-deep` ## Notes * Provider pricing is documented in the web search pricing tables. * BYOK support is provider-dependent; see [BYOK](/api-reference/miscellaneous/byok). * If Brave model IDs appear in `GET /api/v1/models`, you can use them like any other model ID. # Bring Your Own Key (BYOK) Source: https://docs.nano-gpt.com/api-reference/miscellaneous/byok Route chat completions through your own provider API keys. ## Overview BYOK (Bring Your Own Key) lets you store API credentials for supported upstream providers and then opt-in per request to route through your own keys. Typical reasons to use BYOK: * Your provider bills you directly (enterprise agreements, committed-use discounts, free tiers). * You want NanoGPT routing + enhancements, without using NanoGPT platform keys. ## Availability BYOK is currently supported on: * `POST /api/v1/chat/completions` ## Pricing When you use BYOK: * Your provider bills you directly for usage on their side. * NanoGPT charges a **5% platform fee** on top for routing and platform features. ## Configure Keys ### Web UI (Recommended) Manage BYOK keys in the NanoGPT web app: * [https://nano-gpt.com/byok](https://nano-gpt.com/byok) Keys are never shown again after saving (only a short suffix is displayed), so treat them like passwords. ### API (NanoGPT key required) If you prefer managing keys programmatically, use these endpoints (not OpenAI-compatible): ```http theme={null} POST /api/user/provider-keys GET /api/user/provider-keys DELETE /api/user/provider-keys?provider= ``` Example: add/update a provider key ```bash theme={null} curl -X POST "https://nano-gpt.com/api/user/provider-keys" \ -H "Authorization: Bearer YOUR_NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "openai", "key": "YOUR_PROVIDER_API_KEY" }' ``` ## Use BYOK On A Request ### Enable BYOK Option A: request header ```http theme={null} x-use-byok: true ``` Option B: request body ```json theme={null} { "byok": { "enabled": true } } ``` ### Select a provider (optional) If the model can route through multiple providers, you can force a specific provider. Header: ```http theme={null} x-byok-provider: openai ``` Body: ```json theme={null} { "byok": { "enabled": true, "provider": "openai" } } ``` ### Fallbacks (optional) BYOK defaults to **fail-fast** behavior. If your provider key fails, NanoGPT will not silently route the request through a different provider key. You can control this via the request body: ```json theme={null} { "byok": { "enabled": true, "disableFallbacks": true } } ``` ## Provider Slugs The exact set of supported providers can evolve; the BYOK UI lists what your account can configure. ### Chat / completion providers These providers can be used for BYOK on `POST /api/v1/chat/completions`: | Provider | Slug | Key format | | ----------------------- | ------------------------- | ----------------------------------------------------------------------------------- | | OpenAI | `openai` | API key string (`sk-...`) | | OpenAI Responses | `openai-responses` | API key string (`sk-...`) | | Anthropic | `anthropic` | API key string (`sk-ant-...`) | | OpenRouter | `openrouter` | API key string (`or-...`) | | Chutes | `chutes` | API key string (`cpk_...`) | | AWS Bedrock | `aws` | JSON: `{"accessKeyId":"...","secretAccessKey":"...","region":"us-east-1"}` | | Azure OpenAI | `azure` | JSON: `{"endpoint":"...","apiKey":"...","deploymentName":"...","apiVersion":"..."}` | | Azure Responses | `azure-responses` | JSON: `{"endpoint":"...","apiKey":"...","apiVersion":"..."}` | | Azure Anthropic Foundry | `azure-anthropic-foundry` | JSON: `{"endpoint":"...","apiKey":"..."}` | | Google AI Studio | `google` | API key string (`AIza...`) | | Groq | `groq` | API key string (`gsk_...`) | | NVIDIA | `nvidia` | API key string (`nvapi-...`) | | SambaNova | `sambanova` | API key string | | Vercel | `vercel` | API key string | | Novita | `novita` | API key string (`nvta_...`) | | Akash | `akash` | API key string (`sk-...`) | | Z.AI (GLM) | `zai` | API key string | | GMICloud | `gmicloud` | API key string | | Cerebras | `cerebras` | API key string (`csk-...`) | | MegaNova | `meganova` | API key string | Notes: * AWS JSON can also include `sessionToken`. * Azure-style providers require provider-specific endpoints and identifiers (for example, a deployment name). * If you want to force Google AI Studio routing for Gemini models, set `x-byok-provider: google` (or `byok.provider: "google"`). * Additional provider options may appear over time in the BYOK UI and provider-discovery endpoints. ### Web-search-only providers These providers are BYOK for web search enhancements, not chat model execution: | Provider | Slug | | ---------- | ------------ | | Tavily | `tavily` | | Exa | `exa` | | Kagi | `kagi` | | Perplexity | `perplexity` | | Valyu | `valyu` | ## Teams (BYOK) Teams can also store provider keys and configure whether team-billed traffic should use team keys. Team settings endpoints (session-authenticated, not OpenAI-compatible): ```http theme={null} GET /api/teams/{teamUuid}/byok-settings PATCH /api/teams/{teamUuid}/byok-settings GET /api/teams/{teamUuid}/provider-keys POST /api/teams/{teamUuid}/provider-keys DELETE /api/teams/{teamUuid}/provider-keys?provider= ``` BYOK modes: * `disabled`: BYOK off for the team. * `prefer_team`: use a team key when available; otherwise fall back to normal routing. * `require_team`: fail requests when no team key exists for the required provider. Important behavior: * In `prefer_team` mode, team-billed traffic will not use a member's personal BYOK keys unless the client explicitly opts in to BYOK on the request. ## Security Notes * Never put provider keys in client-side code or public repos. * Prefer storing keys once (UI or the `/api/user/provider-keys` endpoints) and then enabling BYOK per request. # Chrome Extension Source: https://docs.nano-gpt.com/api-reference/miscellaneous/chrome-extension Information about the NanoGPT Chrome Extension # Chrome Extension The [NanoGPT Chrome Extension](https://chromewebstore.google.com/detail/nanogpt-search-extension/ajikfdfilhfkakicgdnjhklpfcohibfg?pli=1) is now available! Query NanoGPT from any website by hitting ALT + S (Windows) or CMD + S (Mac)! See also [this short Youtube video](https://www.youtube.com/watch?v=gndGYqvnQTY). # Context Memory Source: https://docs.nano-gpt.com/api-reference/miscellaneous/context-memory Lossless, hierarchical episodic memory for unlimited AI conversations ## Overview Large Language Models are limited by their context window. As conversations grow, models forget details, degrade in quality, or hit hard limits. **Context Memory** solves this with lossless, hierarchical compression of your entire conversation history, enabling unlimited-length coding sessions and conversations while preserving full awareness. ## The Problem Traditional memory solutions are semantic and store general facts. They miss episodic memory: recalling specific events at the right level of detail. Simple summarization drops critical details, while RAG surfaces isolated chunks without surrounding context. Without proper episodic memory: * Important details get lost during summarization * Conversations are cut short when context limits are reached * Agents lose track of previous work ## How It Works Context Memory builds a tree where upper levels contain summaries and lower levels preserve verbatim detail. Relevant sections are expanded while others remain compressed: * High-level summaries provide overall context * Mid-level sections explain relationships * Verbatim details are retrieved precisely when needed Example from a coding session: ``` Token estimation function refactoring ├── Initial user request ├── Refactoring to support integer inputs ├── Error: "exceeds the character limit" │ └── Fixed by changing test params from strings to integers └── Variable name refactoring ``` Ask, "What errors did we encounter?" and the relevant section expands automatically—no overload, no missing context. ## Benefits * **For Developers**: Long coding sessions without losing context; agents learn from past mistakes; documentation retains project-wide context * **For Conversations**: Extended discussions with continuity; research that compounds; complex problem-solving with full history ## Use Cases * **Role‑playing and Storytelling**: Preserve 500k+ tokens of story history while delivering 8k–20k tokens of perfectly relevant context * **Software Development**: Summaries keep the big picture; verbatim code snippets are restored only when needed—no overload, no omissions ## Using Context Memory You can enable Context Memory in the `POST /v1/chat/completions` endpoint in two ways: * **Model suffix**: Append `:memory` to any model name * **Header**: Add `memory: true` * **Combine**: Use with web search via `:online:memory` For complete suffix composition rules, see [Model Suffixes](/api-reference/miscellaneous/model-suffixes). ```python Python theme={null} import requests BASE_URL = "https://nano-gpt.com/api/v1" API_KEY = "YOUR_API_KEY" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } # Use the :memory suffix payload = { "model": "openai/gpt-5.6-sol:memory", "messages": [ {"role": "user", "content": "Remember our plan and continue from step 3."} ] } r = requests.post(f"{BASE_URL}/chat/completions", headers=headers, json=payload) print(r.json()) ``` ```javascript JavaScript theme={null} const BASE_URL = "https://nano-gpt.com/api/v1"; const API_KEY = "YOUR_API_KEY"; // Enable memory via header const res = await fetch(`${BASE_URL}/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', 'memory': 'true' }, body: JSON.stringify({ model: 'openai/gpt-5.6-sol', messages: [{ role: 'user', content: 'Continue the previous discussion but keep all earlier decisions.' }] }) }); const data = await res.json(); ``` ```bash cURL theme={null} # Memory + Web Search combined curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol:online:memory", "messages": [ {"role": "user", "content": "Research alternatives we discussed and continue our plan."} ] }' ``` ## Retention and Caching * **Default retention**: 30 days. * **Configure via model suffix**: `:memory-` where `` is 1..365 * Example: :memory-90 * **Configure via header**: `memory_expiration_days: ` (1..365) * Example: ```bash theme={null} curl -X POST https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -H "memory: true" \ -H "memory_expiration_days: 45" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [{"role": "user", "content": "Hello"}] }' ``` * **Precedence**: If both suffix and header are provided, the header value takes precedence for retention. * **Data lifecycle**: The compressed chat state is retained server‑side for the configured period (or until you delete the conversation). When you delete conversations locally, no memory data persists on Polychat’s systems. * **Caching**: The Context Memory backend may cache portions of repeated prefixes and report them as cached input tokens in usage. Cached input tokens (when present) are billed at the cached input rate (see Pricing below). ## Technical Details Context Memory is implemented as a B‑tree with lossless compression over message histories. Upper nodes store summaries; leaves retain verbatim excerpts relevant to recent turns. Retrieval returns details contextualized by their summaries—unlike RAG which returns isolated chunks. Using messages as identifiers supports: * Natural conversation branching * Easy reversion to earlier states * No complex indexing Compression targets 8k–20k tokens of output—about 10% of Claude’s context window—while preserving access to full history. ## Privacy & Partnership We partner with **Polychat** to provide this technology. * API usage of Context Memory does not send data to Google Analytics or use cookies * Only your conversation messages are sent to Polychat for compression * No email, IP address, or other metadata is shared beyond prompts * When you delete conversations locally, no memory data persists on Polychat’s systems See Polychat’s full privacy policy at [https://polychat.co/legal/privacy](https://polychat.co/legal/privacy). ## Pricing Context Memory is billed as a separate feature, in addition to your model inference costs. * **Input (non-cached)**: \$3.75 per 1M tokens * **Input (cached)**: \$1.00 per 1M tokens * **Output**: \$1.25 per 1M tokens Typical usage is often \~8k–20k tokens per session, but depends on your conversation length and how much needs to be expanded. ## Getting Started 1. Append `:memory` to any model name 2. Or send the `memory: true` header 3. Optionally combine with other features like `:online` Context Memory ensures your AI remembers everything that matters—for coding, research, and long‑form conversations. # Distillation Policy Source: https://docs.nano-gpt.com/api-reference/miscellaneous/distillation-policy Identify text models and provider routes whose outputs may be used for model distillation or training # Distillation Policy NanoGPT exposes a `distillationPolicy` field that indicates whether a model or provider route is allowed for output-based model training or distillation under NanoGPT's recorded model-license and provider-terms rules. This metadata is currently API-only. The text models browser UI does not show a distillation filter or badge. Distillation policy metadata is based on NanoGPT's current interpretation of published model licenses and provider terms. Users are responsible for ensuring their use complies with all applicable terms and laws. ## Policy Object Text model records and provider rows may include: ```json theme={null} { "distillationPolicy": { "status": "allowed", "label": "License permits distillation", "basis": "permissive-open-weights", "sourceUrl": "https://example.com/license-or-terms", "note": "Short explanation of the policy signal." } } ``` All fields are additive and optional for backwards compatibility. New API responses include `distillationPolicy` where NanoGPT has recorded policy metadata. | Field | Type | Description | | ----------- | ------ | ----------------------------------------------------------------------- | | `status` | string | One of `allowed`, `disallowed`, or `unknown`. | | `label` | string | Human-readable policy summary. | | `basis` | string | Rule basis used to derive the status. | | `sourceUrl` | string | License or provider terms URL used as the policy source, when recorded. | | `note` | string | Short explanation and caveats for the policy signal. | ## Statuses | Status | Meaning | | ------------ | ------------------------------------------------------------ | | `allowed` | Distillation is allowed under the current recorded rule set. | | `disallowed` | Distillation is restricted by model or provider terms. | | `unknown` | NanoGPT does not have a clear enough signal. | ## Bases | Basis | Meaning | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `deepseek-api-terms` | DeepSeek explicitly allows use of inputs and outputs for training other models, including distillation. | | `permissive-open-weights` | The underlying model or license permits distillation or derivative model use. | | `provider-output-use-terms` | The provider terms explicitly permit output use. | | `provider-output-use-restriction` | The provider terms restrict output-based training, distillation, model development, or competing model/service creation. | | `closed-provider-restriction` | The model is from a closed provider family that commonly restricts using outputs for competing model training. | | `provider-terms-unknown` | No clear provider or model policy is recorded. | ## Model Policy vs Provider Policy There are two policy layers: | Layer | Question it answers | | -------------------- | ---------------------------------------------------------------------------------------- | | Model/license policy | Does the underlying model or model family allow distillation? | | Provider/API policy | Does the specific hosted provider route allow users to use API outputs for distillation? | A provider can restrict output use even when the underlying model license is permissive. Provider-specific restrictions override model-level allowances. Provider rows are evaluated with these rules: * Explicit provider restrictions return `disallowed`. * Explicit provider permission returns `allowed`. * If provider terms do not mention output-based training or distillation, and the underlying model/license allows it, NanoGPT treats the provider route as `allowed`. * If the underlying model policy is `unknown` or `disallowed`, provider silence does not make it allowed. Example provider row: ```json theme={null} { "provider": "fireworks", "distillationPolicy": { "status": "allowed", "label": "Provider terms permit output use", "basis": "provider-output-use-terms", "sourceUrl": "https://fireworks.ai/terms-of-service", "note": "Fireworks permits use of outputs for any lawful purpose, subject to model-provider license and usage restrictions. Model-level signal: license permits distillation." } } ``` ## Filtering Text Models The explore text model endpoints support `distillation=allowed`. ```http theme={null} GET /api/explore/text-models?distillation=allowed ``` This returns only text models whose model-level distillation policy is `allowed`. Search supports the same parameter for text search: ```http theme={null} GET /api/explore/search?type=text&q=qwen&distillation=allowed ``` The filter applies only to text models. ## Response Metadata When the filter is used, responses include: ```json theme={null} { "meta": { "distillation": "allowed" } } ``` Without the filter, text model responses include: ```json theme={null} { "meta": { "distillation": "all" } } ``` ## Provider-Level Behavior Provider route policy can differ from model-level policy: * DeepSeek models served by DeepSeek are `allowed`. * Fireworks, Together, Nebius, and ArliAI routes on a model whose license permits distillation are `allowed`. * Providers with explicit competing-model or output-training restrictions are `disallowed`. * Providers with no recorded restriction on an allowed open-weight model are `allowed`. * Providers with no recorded restriction on an unknown model or license are `unknown`. For a representative open Qwen model across user-selectable providers, the current provider-level split is `35 allowed`, `21 disallowed`, and `0 unknown`. ## Caveats * This metadata is informational and is not legal advice. * Provider terms and model licenses can change. * Model licenses can impose attribution, naming, acceptable-use, or derivative-model restrictions even when distillation is allowed. * Provider-specific restrictions override model-level allowances. * Unknown model/license status remains unknown even if a provider is silent. * Inspect `sourceUrl` and `note` before relying on a model or route for distillation. # Error Handling Source: https://docs.nano-gpt.com/api-reference/miscellaneous/error-handling Error formats, status codes, and retry guidance for the NanoGPT API. ## Overview NanoGPT APIs return standard HTTP status codes. Many endpoints also follow OpenAI- or Anthropic-compatible error shapes so you can reuse existing SDK error handling. If you contact support about an API error, include the `X-Request-ID` response header (when present). ## Error Response Formats NanoGPT has a few common error envelopes depending on the API surface. ### OpenAI-compatible (most `/api/v1/*` endpoints) Used by endpoints like: * `POST /api/v1/chat/completions` * `POST /api/v1/responses` * `POST /api/v1/embeddings` * `GET /api/v1/models` ```json theme={null} { "error": { "message": "Human-readable error message", "type": "invalid_request_error", "code": "missing_required_parameter", "param": "model" } } ``` Fields: * `error.message`: human-readable * `error.type`: high-level category (see [Error Types](#error-types)) * `error.code`: optional machine-readable code (see [Error Codes](#error-codes)) * `error.param`: optional name of the request field that caused the error ### Anthropic-compatible (`POST /api/v1/messages`) The Messages API uses the Anthropic-style wrapper: ```json theme={null} { "type": "error", "error": { "type": "invalid_request_error", "message": "max_tokens is required", "param": "max_tokens" } } ``` ### Legacy / simple format (some `/api/*` endpoints) Some older endpoints return a simpler body: ```json theme={null} { "error": "Insufficient balance", "status": 402 } ``` Some responses also include a structured object and convenience fields (for example, `requiredBalance` on `402`). ## Status Codes This table covers the most common HTTP error statuses you may encounter. | Status | Meaning | Typical client action | | ------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `400` | Invalid request / validation failed | Fix the request, then retry | | `401` | Missing/invalid API key | Do not retry until credentials are fixed | | `402` | Insufficient balance | Add funds or disable paid enhancements, then retry | | `403` | Authenticated but not permitted | Choose an allowed model/feature or change permissions | | `404` | Resource not found | Check the model/resource ID | | `408` / `504` | Timeout | Retry with backoff | | `409` | Conflict | Resolve state (duplicate/redeemed/already processed) | | `413` | Payload too large | Reduce payload size and retry | | `429` | Rate limited | Wait (use `Retry-After` if present), then retry. This can be a per-second throughput limit or a per-key daily limit. | | `500` | Server error | Retry with backoff | | `503` | Temporarily unavailable | Retry with backoff; consider changing model if persistent | Notes: * Some endpoints include convenience fields like `status` in the JSON body mirroring the HTTP status code. * Some error responses include `Retry-After` (seconds) for `429`. ## Error Types Depending on the endpoint, you may see different type strings. Common values include: * `invalid_request_error` (400) * `authentication_error` (401) * `permission_denied_error` or `permission_error` (403) * `not_found_error` (404) * `rate_limit_error` (429) * `server_error`, `service_unavailable`, or `api_error` (500/503) ## Error Codes Not every error includes a `code`. When present, codes are useful for programmatic handling. Common codes include: ### Request validation * `missing_required_parameter` * `invalid_parameter_value` * `invalid_json` * `invalid_json_schema` * `tool_choice_unsupported` * `image_input_not_supported` * `conflicting_moderation_model` * `inline_moderation_requires_api_key` * `empty_moderation_input` * `unsupported_moderation_input` * `unsupported_input_modality` * `unsupported_batch_input` ### Content and context * `content_policy_violation` * `context_length_exceeded` * `empty_response` ### Model and routing * `model_not_found` * `model_not_allowed` * `model_not_available` * `all_fallbacks_failed` * `no_fallback_available` * `fallback_blocked_for_cache_consistency` ### Balance and payment * `memory_balance_required` * `webSearch_balance_required` * `both_balance_required` ### Rate limiting * `rate_limit_exceeded` * `daily_rpd_limit_exceeded` * `daily_usd_limit_exceeded` ## Streaming Errors When using Server-Sent Events (`"stream": true`), errors can happen: * Before streaming begins (you get a normal JSON error response with an HTTP status code) * Mid-stream (you may receive an error frame/chunk, or the connection may terminate) Example (mid-stream error frame): ```text theme={null} data: {"id":"chatcmpl_...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"error"}],"error":{"status":503,"message":"Service temporarily unavailable. Please try again later.","code":"service_unavailable"}} data: [DONE] ``` If an error happens mid-stream, treat it as a failed request: * Surface/log the error message * Log the `X-Request-ID` header if available * Retry only when the status/category indicates it is safe to retry (for example 408/429/500/503) ## Retry Guidance General retry recommendations: * Retry: `408`, `429`, `500`, `503` (use exponential backoff and respect `Retry-After`) * Do not blindly retry: `400`, `401`, `402`, `403`, `404`, `409`, `413` Suggested backoff (example): ```text theme={null} Attempt 1: 1s Attempt 2: 2s Attempt 3: 4s ``` Add jitter to avoid synchronized retries. If you need current guidance on global rate limits, see [Rate Limits](/api-reference/miscellaneous/rate-limits). ## Daily Per-Key Limit Notes NanoGPT can enforce optional per-key daily limits (Requests/Day and USD/Day). When a daily limit is exceeded, the API returns `429` and typically includes a `Retry-After` header indicating the number of seconds until the next reset at **00:00 UTC**. ## Balance Errors (402) Some endpoints return `402 Payment Required` when payment is needed before the request can run. There are two common cases. ### Authenticated Balance Error ```json theme={null} { "error": "Insufficient balance", "requiredBalance": 0.0035, "status": 402 } ``` This is the ordinary insufficient-balance response for authenticated requests. ### Accountless x402 Payment Challenge Supported endpoints can also be called without `Authorization` or `x-api-key` when the client explicitly opts in to accountless x402 quote generation. To request an accountless x402 quote, send the API request without `Authorization` or `x-api-key`, and include `x-x402: true`. NanoGPT will return `402 Payment Required` with available payment options and a stable top-level `payment` object plus legacy OpenAI-compatible error fields. If you receive `401 missing_api_key` immediately, check that the initial quote request includes `x-x402: true`. Without that header, NanoGPT does not enter the x402 quote flow. Detect accountless x402 by checking for the top-level `payment` object, not a single `error.code` value. ```json theme={null} { "error": { "type": "insufficient_quota", "code": "insufficient_quota", "message": "Payment required to complete this request." }, "payment": { "version": 1, "paymentId": "pay_...", "requestHash": "sha256:...", "expiresAt": "2026-06-09T12:00:00.000Z", "amountUsd": "0.0714", "statusUrl": "https://nano-gpt.com/api/x402/status/pay_...", "completeUrl": "https://nano-gpt.com/api/x402/complete/pay_...", "accepted": [ { "scheme": "nano", "protocolScheme": "nano", "network": "nano-mainnet", "amount": "...", "amountFormatted": "0.17067988 XNO", "amountUsd": "0.0714", "payTo": "nano_...", "paymentId": "pay_...", "statusUrl": "https://nano-gpt.com/api/x402/status/pay_...", "completeUrl": "https://nano-gpt.com/api/x402/complete/pay_..." }, { "scheme": "x402-solana-usdc", "protocolScheme": "exact", "network": "solana", "amount": "1500", "amountFormatted": "0.0015 USDC", "amountUsd": "0.0015", "payTo": "", "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "feePayer": "", "paymentId": "pay_...", "expiresAt": "2026-06-09T12:00:00.000Z" }, { "scheme": "lightning-l402", "protocolScheme": "lightning-l402", "network": "bitcoin-lightning", "amount": "9", "amountFormatted": "9 sats", "amountUsd": "0.0054", "payTo": "lnbc...", "invoice": "lnbc...", "paymentHash": "...", "l402Token": "..." } ] }, "x402Version": 1, "accepts": [] } ``` The legacy `accepts` array may still appear for backwards-compatible and lower-level protocol clients, but new clients should use `payment.accepted[]`. Exact rails such as `nano-exact`, `x402-exact`, and `x402-solana-usdc` replay the original request with `X-PAYMENT`; polling-style rails such as `nano` and `base-usdc` use `statusUrl` and `completeUrl`; Lightning L402 replays the exact original request with `Authorization: L402 :` and does not use `X-PAYMENT` or `completeUrl`. See [Accountless x402 API Payments](/api-reference/miscellaneous/x402) for the complete flow and supported endpoint matrix. Feature-specific variants may include a structured error `code` such as `memory_balance_required` or `webSearch_balance_required`. ## Content Policy and Empty Responses * If a request is blocked by safety checks (`content_policy_violation`), the API will return a `400` error and is intended to avoid charging for the blocked generation request. If [Inline Moderation](/api-reference/miscellaneous/inline-moderation) blocked the request, the separate moderation preflight is still charged. * If the API returns `empty_response`, the request is intended to avoid charging (common causes: stop sequences, very low `max_tokens`, or filtering). ## BYOK Notes When using BYOK (Bring Your Own Key), some error messages may reference "your API key" because the upstream credentials belong to you. # Extended Thinking (Reasoning) Source: https://docs.nano-gpt.com/api-reference/miscellaneous/extended-thinking How NanoGPT surfaces and controls reasoning output across OpenAI-compatible endpoints ## Overview Some models generate a separate **reasoning** stream (sometimes called *thinking*) in addition to the final **answer** content. NanoGPT exposes this in an OpenAI-compatible way for Chat Completions: * Streaming (SSE): `choices[0].delta.reasoning` (or legacy `choices[0].delta.reasoning_content`) * Non-streaming: `choices[0].message.reasoning` (or legacy `choices[0].message.reasoning_content`) Not every model exposes reasoning text. If a model does not emit a reasoning stream, these fields may be absent even if the model internally "reasons". `:thinking` is model-specific and only works when that exact ID (or a documented alias) exists. `-thinking` is a legacy alias pattern for some model families only, not universal. Do not assume `-thinking` works for arbitrary model IDs. Always check `GET /api/v1/models` for exact valid IDs. ## Endpoint Variants (Chat Completions) NanoGPT provides three base paths for chat completions. All accept the same request format and model names, but differ in how reasoning content is delivered: | Base URL | Behavior | Use When | | ---------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------- | | `/api/v1/chat/completions` | Reasoning and answer are separate fields (`reasoning` + `content`). | Most OpenAI-compatible clients | | `/api/v1legacy/chat/completions` | Same as `/api/v1/`, but uses the legacy field name `reasoning_content`. | Clients that only parse `reasoning_content` | | `/api/v1thinking/chat/completions` | Reasoning and answer are merged into the normal `content` stream. | Clients that ignore reasoning fields but should still display thoughts | This is the same behavior documented under **Reasoning Streams** on the [Chat Completion](/api-reference/endpoint/chat-completion) page. ## Controlling Reasoning Output ### Hide Reasoning To strip reasoning from the response (both streaming and non-streaming), send: ```json theme={null} { "reasoning": { "exclude": true } } ``` `reasoning.exclude` controls output visibility. It is not the same as disabling reasoning compute. Or append the model suffix: * `:reasoning-exclude` Example: ```json theme={null} { "model": "anthropic/claude-opus-4.6:reasoning-exclude", "messages": [{ "role": "user", "content": "What is 2+2?" }] } ``` ### Reasoning Effort `reasoning_effort` (or `reasoning.effort`) controls reasoning depth and also acts as an explicit reasoning-mode signal. Any value other than `none` is treated as a request to enable reasoning/thinking behavior. Use `none` to explicitly disable reasoning behavior. ```json theme={null} { "reasoning_effort": "high" } ``` Or: ```json theme={null} { "reasoning": { "effort": "high" } } ``` Both formats are accepted. If both are present, top-level `reasoning_effort` is authoritative for Chat Completions request shaping. Valid values are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`. ### Legacy Field Name Compatibility (`reasoning_content`) If a client expects `reasoning_content` instead of `reasoning`, you can: 1. Use `/api/v1legacy/chat/completions`, or 2. Set `reasoning.delta_field: "reasoning_content"`, or 3. Use the shorthands `reasoning_delta_field` / `reasoning_content_compat`. ## How Reasoning Appears in Responses ### Streaming (SSE) Reasoning deltas appear before or alongside content deltas: ```text theme={null} data: {"choices":[{"index":0,"delta":{"reasoning":"Thinking..."},"finish_reason":null}]} data: {"choices":[{"index":0,"delta":{"content":"Here is the answer."},"finish_reason":null}]} ``` ### Non-Streaming The final message may include a separate reasoning field: ```json theme={null} { "choices": [ { "message": { "role": "assistant", "content": "Here is the answer.", "reasoning": "Thinking..." } } ] } ``` ## Cost Notes Reasoning tokens are billed as output tokens. If you enable higher reasoning effort (or use thinking variants), expect higher `completion_tokens` and higher cost. ## See Also * Chat Completions: reasoning controls and endpoint variants ([Chat Completion](/api-reference/endpoint/chat-completion)) * Streaming protocol details across endpoints ([Streaming Protocol](/api-reference/miscellaneous/streaming-protocol)) # For Providers Source: https://docs.nano-gpt.com/api-reference/miscellaneous/for-providers Information for model providers We work together with many providers. If you think you offer models that we don't have yet, can offer models for a lower price or have some other reason we should integrate you, feel free to reach out to us either at [support@nano-gpt.com](mailto:support@nano-gpt.com) or find us on our [Discord](https://discord.gg/KaQt8gPG6V). To speed things up, mention the following in your email: * Company name * How to best get in touch with you. We mostly use Discord, email and Twitter in that order. * Why should we add you? * Do you have low prices? * Any models others don't have? * Any volume discounts you can offer? * Do you run models in a special way (decentralized backend, etc) * What rate limits do you have? * What is your privacy policy? Do you log prompts? If so, how long do you store them? * Do you support credit card payments? Crypto payments? Automatic payments? * Do you have an OpenAI compatible endpoint? * Any other information that seems relevant # Hosted tool search Source: https://docs.nano-gpt.com/api-reference/miscellaneous/hosted-tool-search Let compatible models discover relevant functions from large deferred tool catalogs on demand. Hosted tool search lets a compatible model search a large function catalog and load only the definitions it needs. This can reduce the input tokens used by tool definitions and helps keep the initial prompt prefix stable when the catalog changes. The pilot is available on the [Responses API](/api-reference/endpoint/responses). Add one `nanogpt:tool_search` entry (the shorter `tool_search` alias is also accepted), then mark functions that should be discovered on demand with `defer_loading: true`. Hosted tool search discovers function definitions. It does not execute a client function, grant permission to use it, or bypass an approval flow. ## How it works 1. Your request includes the search tool and the complete set of functions the caller is already authorized to use. 2. Eager functions remain visible immediately. Deferred functions are searched and revealed on demand. 3. Search activity appears in the response as a `tool_search_call` output item. 4. When the model selects a revealed function, it returns a normal `function_call` item. 5. Your client executes that function under its normal authorization and approval rules, then sends a normal `function_call_output` item. Search is limited to the function definitions submitted in the request. Callers must submit only functions the user is authorized to use. Tool names, descriptions, and schemas should still be treated as untrusted application input. ## Request example ```bash theme={null} curl https://api.nano-gpt.com/api/v1/responses \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.5", "input": "What is the weather in Amsterdam?", "tools": [ { "type": "nanogpt:tool_search", "max_results": 5 }, { "type": "function", "name": "weather_forecast", "description": "Get the weather forecast for a city.", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] }, "defer_loading": true }, { "type": "function", "name": "calendar_events_find", "description": "Find calendar events.", "parameters": { "type": "object", "properties": { "query": { "type": "string" } } }, "defer_loading": true } ] }' ``` A compatible response can contain both discovery and the selected function call: ```json theme={null} { "output": [ { "type": "tool_search_call", "id": "ts_123", "status": "completed" }, { "type": "function_call", "id": "fc_123", "call_id": "call_abc123", "name": "weather_forecast", "arguments": "{\"city\":\"Amsterdam\"}", "status": "completed" } ] } ``` Execute `weather_forecast` in your application only after applying the same authorization and approval checks you use for eager tools. Return its result with a normal `function_call_output` continuation. ## `tool_choice` Hosted tool search supports: * Omitted `tool_choice` * `"auto"` * A Responses `allowed_tools` choice It cannot be combined with `"none"`, `"required"`, or a forced function choice. These combinations return `tool_search_tool_choice_conflict` instead of silently changing the request. If you use `allowed_tools`, the allowlist must remain consistent with the functions the caller is authorized to use. Discovery never expands that authorization boundary. ## Limits and compatibility | Limit | Current pilot behavior | | ---------------------- | ------------------------------------------------------------------------------------------------- | | API | Responses API only | | Search entries | Exactly one when deferred functions are present | | Deferred functions | At least one | | Complete `tools` array | Maximum 2,000 entries | | `max_results` | Integer from 1 to 50; default 5 | | Tool names | Unique, case-insensitively | | Background requests | `background: true` is not supported | | Other hosted tools | Cannot be mixed with web search, X search, MCP, namespace, or other hosted built-ins in the pilot | | Model support | Capability-dependent; unsupported models fail explicitly | The search entry itself cannot set `defer_loading: true`. Chat Completions and Anthropic Messages do not support this NanoGPT hosted-search contract and return `tool_search_api_not_supported` when it is requested. Existing MCP integrations remain available outside this pilot. If your client converts already-authorized MCP definitions into ordinary Responses function tools, those function definitions can participate in hosted search; the client remains responsible for execution, credentials, authorization, and approval. Do not include MCP credentials in function names, descriptions, or schemas. Use [`GET /api/v1/agent-capabilities`](https://nano-gpt.com/api/v1/agent-capabilities) and inspect `toolSearch` for the current public contract, aliases, API support, and limits. Model support is still evaluated per request. ## Prompt caching Sending every function eagerly makes the full catalog part of the initial prompt prefix. Adding, removing, or editing an eager definition changes that prefix. With hosted search, the small search-tool definition stays in the initial prefix and relevant deferred definitions are revealed later. This improves the opportunity for prompt-cache reuse and usually reduces initial input tokens for large catalogs. It does not guarantee a cache hit: model/provider thresholds, TTLs, routing, and exact-prefix requirements still apply. ## Billing Hosted search has no separate NanoGPT fee. Normal model input and output usage remains billable, including model work involved in discovery. Any applicable hosted-tool charge is also included in normal request accounting. Usage and `x_nanogpt_pricing` continue to follow the normal Responses contract. Use `max_tool_calls` and client-side cancellation or turn limits to bound the overall agent workflow. ## Errors Errors use the standard NanoGPT error envelope. Hosted-search validation can return: | Code | Meaning | | ---------------------------------------------- | ----------------------------------------------------------------- | | `tool_search_required` | A function uses `defer_loading` without a search entry | | `deferred_tools_required` | A search entry has no deferred function to search | | `duplicate_tool_search` | More than one search entry was supplied | | `duplicate_tool_name` | Function names collide case-insensitively | | `invalid_defer_loading` | `defer_loading` is not a boolean | | `tool_search_cannot_be_deferred` | The search entry itself is deferred | | `invalid_tool_search_max_results` | `max_results` is outside the integer range 1–50 | | `tool_search_catalog_too_large` | The complete tools array exceeds 2,000 entries | | `tool_search_tool_choice_conflict` | `tool_choice` would prevent or force a conflicting call | | `tool_search_model_not_supported` | The selected model cannot perform hosted search | | `tool_search_api_not_supported` | The request uses an unsupported API surface | | `tool_search_background_not_supported` | Hosted search was combined with background mode | | `tool_search_mixed_hosted_tools_not_supported` | The pilot was mixed with another hosted or non-function tool type | No match is a valid search outcome. The model may search again, answer without a function, or return an ordinary incomplete/failed response. Clients should enforce their own turn and tool-call limits, and handle cancellation and partial streams using the normal [streaming protocol](/api-reference/miscellaneous/streaming-protocol). # Inline Moderation Source: https://docs.nano-gpt.com/api-reference/miscellaneous/inline-moderation Run a paid input safety preflight before selected generation requests ## Overview Inline moderation lets authenticated API-key callers ask NanoGPT to run a paid input safety check before a text, image, or video generation request is dispatched to the target model provider. It is opt-in per request and configured with HTTP headers. If the submitted input is flagged, NanoGPT charges only the moderation check, skips generation, and returns a content policy error. If the input passes, NanoGPT charges the moderation check, then continues with the normal generation request and normal generation billing. Inline moderation is an input preflight only. It does not moderate generated outputs. ## Supported Routes Inline moderation is supported on authenticated API-key requests to: * `POST /api/v1/chat/completions` * `POST /api/v1/responses` * `POST /api/v1/completions` * `POST /api/v1/messages` * `POST /api/v1/images/generations` * `POST /api/v1/images/edits` * NanoGPT image generation compatibility routes that flow through the canonical image handler * NanoGPT video generation requests that flow through the canonical video handler Accountless x402/exact-payment requests are not supported for inline moderation in this version. ## Enabling Inline Moderation Use the `moderation` header. To enable moderation with the default moderation model: ```http theme={null} moderation: true ``` Truthy values all enable the default moderation model: * `true` * `1` * `yes` * `on` Falsy values disable inline moderation: * `false` * `0` * `no` * `off` ## Selecting a Moderation Model Supported moderation model IDs are listed by: ```http theme={null} GET /api/v1/moderation-models ``` There are two ways to choose a moderation model. Pass the model ID directly in the `moderation` header: ```http theme={null} moderation: meta-llama/llama-guard-4-12b ``` Or enable moderation with `moderation: true` and select the model with `moderation-model`: ```http theme={null} moderation: true moderation-model: meta-llama/llama-guard-4-12b ``` `moderation-model` only selects the model when the `moderation` header is also present. A request with only `moderation-model` does not enable inline moderation. If both headers specify model IDs and they differ, the request fails: ```json theme={null} { "error": { "message": "Conflicting moderation headers.", "type": "invalid_request_error", "code": "conflicting_moderation_model", "param": "moderation" } } ``` Unknown moderation model IDs use the normal `model_not_found` behavior. ## What Gets Moderated Inline moderation checks user-facing request input before generation. For text APIs, NanoGPT moderates: * chat messages * Responses API instructions * Responses API input, including merged `conversation.messages` and `previous_response_id` history when applicable * Responses API text content parts such as `input_text`, `output_text`, and tool output text * completions prompts, including prompt arrays * image content parts when they are provided as HTTP(S) image URLs or `data:image/...` URLs For image and video APIs, NanoGPT moderates: * `prompt` * multi-prompt text fields * submitted images * reference images * start/end/first/last frame images when those fields are used For image and video requests, submitted images must be inspectable as HTTP(S) image URLs or `data:image/...` URLs. If a request includes an image input that cannot be inspected, such as a bare base64 string or a provider-specific file/asset reference, inline moderation fails closed with `unsupported_moderation_input`. NanoGPT does not moderate: * generated outputs * raw video inputs * provider configuration fields * `negative_prompt` * arbitrary non-user-facing model configuration If moderation is requested but NanoGPT cannot extract any text or image input to moderate, the request fails with `empty_moderation_input`. ## Billing Behavior Inline moderation is a separate paid preflight. If the moderation check passes: * NanoGPT charges the moderation request. * NanoGPT continues with normal generation. * Normal generation billing still applies. If the moderation check flags the input: * NanoGPT charges the moderation request. * NanoGPT does not dispatch the generation request to the target provider. * NanoGPT does not charge generation. * The response is `400 content_policy_violation`. If the moderation provider is unavailable or returns malformed output: * NanoGPT fails closed with `503`. * NanoGPT does not charge moderation. * NanoGPT does not dispatch generation. * NanoGPT does not charge generation. Inline moderation is currently supported only for authenticated API-key billing. Accountless x402/exact-payment requests return: ```json theme={null} { "error": { "message": "Inline moderation is currently supported for authenticated API-key requests only.", "type": "invalid_request_error", "code": "inline_moderation_requires_api_key", "param": "moderation" } } ``` ## Response Headers When inline moderation runs and the request is not blocked, successful moderated generation responses include moderation metadata headers: ```http theme={null} x-nanogpt-inline-moderation-model: omni-moderation-latest x-nanogpt-inline-moderation-flagged: false x-nanogpt-inline-moderation-cost-usd: 0.00000123 ``` When inline moderation blocks a request, the blocked response also includes: ```http theme={null} x-nanogpt-inline-moderation-model: omni-moderation-latest x-nanogpt-inline-moderation-flagged: true x-nanogpt-inline-moderation-cost-usd: 0.00000123 ``` Whitelabel responses scrub NanoGPT-specific response headers. ## Blocked Response Shape When moderation flags the input, NanoGPT returns `400` with a standard error object and moderation metadata. The original input content is not echoed. ```json theme={null} { "error": { "message": "Your request was blocked by content moderation.", "type": "invalid_request_error", "code": "content_policy_violation" }, "moderation": { "id": "modr_abc123", "model": "omni-moderation-latest", "results": [ { "flagged": true, "categories": { "sexual": true }, "category_scores": { "sexual": 0.98 } } ], "usage": { "prompt_tokens": 120, "completion_tokens": 0, "total_tokens": 120 } } } ``` Exact category names and score shapes depend on the moderation model. ## Common Errors | Code | Meaning | | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `conflicting_moderation_model` | Both `moderation` and `moderation-model` specify different moderation model IDs. | | `model_not_found` | The selected moderation model ID is not supported. | | `inline_moderation_requires_api_key` | Inline moderation was requested without a supported authenticated API-key billing context. | | `empty_moderation_input` | Moderation was requested but no text or image input could be extracted. | | `unsupported_moderation_input` | The request contains input that inline moderation cannot inspect safely, such as an unsupported image file reference or non-URL image payload. | | `unsupported_input_modality` | The selected moderation model cannot moderate one of the submitted modalities, for example image input with a text-only moderation model. | | `unsupported_batch_input` | The selected moderation model does not support batched moderation input. | | `context_length_exceeded` | The moderation input exceeds the selected moderation model's input limit. | | `content_policy_violation` | Moderation flagged the input. | ## Examples ### Chat Completions ```bash theme={null} curl https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "moderation: true" \ -d '{ "model": "openai/gpt-4o-mini", "messages": [ { "role": "user", "content": "Write a short product description for a hiking backpack." } ] }' ``` ### Responses API with a Specific Moderation Model ```bash theme={null} curl https://nano-gpt.com/api/v1/responses \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "moderation: true" \ -H "moderation-model: meta-llama/llama-guard-4-12b" \ -d '{ "model": "openai/gpt-4o-mini", "instructions": "Answer clearly and briefly.", "input": "Summarize what inline moderation does." }' ``` ### Image Generation ```bash theme={null} curl https://nano-gpt.com/api/v1/images/generations \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "moderation: true" \ -d '{ "model": "openai/gpt-image-1", "prompt": "A clean product photo of a ceramic coffee mug on a kitchen counter" }' ``` ### Image or Video Request with Reference Images Reference images are moderated when they are provided as inspectable URLs: ```json theme={null} { "model": "some-image-or-video-model", "prompt": "Use this product photo as a style reference.", "referenceImages": [ { "url": "https://example.com/reference.png" } ] } ``` Unsupported image payloads fail closed: ```json theme={null} { "error": { "message": "Inline moderation does not support one or more submitted image inputs. Use an HTTP(S) image URL or data:image URL instead.", "type": "invalid_request_error", "code": "unsupported_moderation_input", "param": "moderation" } } ``` ## Notes for Integrators Inline moderation is best for callers who want NanoGPT to enforce a paid input safety preflight before generation. It is not a substitute for output moderation, downstream user reporting, or product-specific policy controls. Because moderation is charged separately, clients should treat inline moderation as an additional paid feature. If a request is blocked, the caller still pays for moderation, but not for generation. To avoid accidental no-op configuration, always send `moderation: true` or `moderation: ` when you want inline moderation enabled. # JavaScript Library Source: https://docs.nano-gpt.com/api-reference/miscellaneous/javascript Node.js library for interacting with NanoGPT API # NanoGPTJS [NanoGPTJS](https://github.com/kilkelly/nanogptjs) is a Node.js library designed to interact with NanoGPT's API. This library provides an easy way to integrate NanoGPT's capabilities into your JavaScript applications. ## Overview The NanoGPT service enables pay-per-prompt interaction with chat and image generation models. You will need a prefilled NanoGPT wallet and API key to use this library effectively. ## Installation You can install the library using npm: ```bash theme={null} npm install nanogptjs ``` ## Basic Usage ```javascript theme={null} const NanoGPT = require('nanogptjs'); // Initialize with your API key const nanogpt = new NanoGPT('your-api-key'); async function chatExample() { try { const response = await nanogpt.chat({ model: 'openai/gpt-5.6-sol', messages: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'What is the capital of France?' } ] }); console.log(response); } catch (error) { console.error('Error:', error); } } chatExample(); ``` ## Features * **Chat Completions**: Generate text responses using various AI models * **Image Generation**: Create images from text prompts * **Model Selection**: Choose from a wide range of available models * **Balance Management**: Check your NanoGPT balance and manage transactions ## API Methods ### Chat ```javascript theme={null} const response = await nanogpt.chat({ model: 'openai/gpt-5.6-sol', messages: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Hello, how are you?' } ], temperature: 0.7, max_tokens: 150 }); ``` ### Generate Image ```javascript theme={null} const response = await nanogpt.generateImage({ prompt: 'A beautiful sunset over the ocean', model: 'recraft-v3', width: 1024, height: 1024 }); ``` ### Check Balance ```javascript theme={null} const balance = await nanogpt.checkBalance(); console.log('USD Balance:', balance.usd_balance); console.log('Nano Balance:', balance.nano_balance); ``` ## Error Handling The library provides robust error handling to manage API response errors: ```javascript theme={null} try { const response = await nanogpt.chat({ model: 'openai/gpt-5.6-sol', messages: [ { role: 'user', content: 'Hello!' } ] }); } catch (error) { console.error('Status:', error.status); console.error('Message:', error.message); } ``` ## Resources * [GitHub Repository](https://github.com/kilkelly/nanogptjs) * [NanoGPT API Documentation](https://docs.nano-gpt.com) * [Get your API Key](https://nano-gpt.com/api) # Model Context Protocol (MCP) Source: https://docs.nano-gpt.com/api-reference/miscellaneous/mcp-server Integrate NanoGPT into your AI workflows via MCP # NanoGPT MCP Server The NanoGPT MCP Server allows you to integrate NanoGPT's powerful AI capabilities directly into your favorite AI tools and editors that support the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). With this server, you can give your AI agents access to web search, image generation, URL scraping, and NanoGPT's full library of LLMs. ## Installation You can run the NanoGPT MCP server directly using `npx`. No manual installation is required if you have Node.js installed. ```bash theme={null} npx @nanogpt/mcp ``` ### Claude Code To add NanoGPT MCP to Claude Code via the CLI: **macOS / Linux** ```bash theme={null} claude mcp add nanogpt --scope user \ --env NANOGPT_API_KEY=YOUR_API_KEY \ -- npx -y @nanogpt/mcp ``` **Windows** ```powershell theme={null} claude mcp add nanogpt --scope user --env NANOGPT_API_KEY=YOUR_API_KEY -- cmd /c "C:\Program Files\nodejs\npx.cmd" -y @nanogpt/mcp ``` On native Windows, `npx` is usually a `.cmd` shim, so it should be launched through `cmd /c`. If you use WSL, run the macOS/Linux command inside WSL instead of the native Windows one. Replace `YOUR_API_KEY` with your actual NanoGPT API key. ### IDE Integration To use NanoGPT MCP with tools like **Claude Desktop**, **Cline**, or **Cursor**, add the following to your configuration file: ```json theme={null} { "mcpServers": { "nanogpt": { "command": "npx", "args": ["-y", "@nanogpt/mcp"], "env": { "NANOGPT_API_KEY": "your_api_key_here" } } } } ``` ## Configuration The server is configured via environment variables. | Variable | Description | Default | | :---------------------- | :------------------------------------------------------ | :--------------------- | | `NANOGPT_API_KEY` | **Required**. Your NanoGPT API key. | - | | `NANOGPT_BASE_URL` | The base URL for the NanoGPT API. | `https://nano-gpt.com` | | `NANOGPT_AUTH_MODE` | Authentication mode (`bearer`, `x-api-key`, or `both`). | `bearer` | | `NANOGPT_LOG_LEVEL` | Logging verbosity (`debug`, `info`, `warn`, `error`). | `info` | | `NANOGPT_TIMEOUT_MS` | Request timeout in milliseconds. | `120000` | | `NANOGPT_MAX_RETRIES` | Number of times to retry failed requests. | `0` | | `NANOGPT_DEFAULT_MODEL` | Default model to use for chat tools. | - | ## Available Tools The MCP server exposes several tools that your AI agent can use: * **`chat`**: Send messages to any model supported by NanoGPT. * **`image-generation`**: Generate images using models like Flux, DALL-E 3, etc. * **`web-search`**: Search the web and get back clean, LLM-friendly results. * **`scrape-urls`**: Extract content from one or more websites. * **`youtube-transcribe`**: Get transcripts from YouTube videos. * **`balance`**: Check your current NanoGPT account balance. * **`list-models`**: List available text, image, audio, or video models. * **`vision`**: Analyze images using vision-capable models. ## Tool Parameters ### `chat` * `messages` (required): Array of message objects (`role` + `content`) * `model` (optional): Model ID to use for the request * `temperature` (optional): Sampling temperature * `max_tokens` (optional): Maximum tokens in the response ### `image-generation` * `prompt` (required): Text description of the image to generate * `model` (optional): Image model ID * `n` (optional): Number of images to generate (1–10) * `size` (optional): Dimensions (e.g., `1024x1024`) * `quality` (optional): Quality setting (model-dependent) ### `web-search` * `query` (required): Search query string * `depth` (optional): `standard` or `deep` * `fromDate` / `toDate` (optional): Date range filter (`YYYY-MM-DD`) ### `youtube-transcribe` * `urls` (required): Array of YouTube URLs (up to 10 per request) ## Resources Resources allow agents to "read" data stored by the server. NanoGPT MCP provides: * **`nanogpt://raw/{id}`**: Access the raw JSON response from a previous API call. * **`nanogpt://image/{id}`**: Access an image generated during the session. ## Cost & Billing * Paid tools deduct from your NanoGPT account balance. * Model listing operations are complimentary. * Failed requests (timeouts, errors) are not charged. ## Troubleshooting ### API key validation * Ensure `NANOGPT_API_KEY` is set in your MCP client configuration. * Verify the key in your NanoGPT API dashboard and remove any extra spaces. * If Claude Code fails immediately with `Invalid API key`, verify the MCP launcher command too, especially on native Windows where `npx` should run via `cmd /c "C:\Program Files\nodejs\npx.cmd"`. ### Timeouts for long operations Increase the timeout for web search or large jobs with `NANOGPT_TIMEOUT_MS`: ```json theme={null} { "mcpServers": { "nanogpt": { "command": "npx", "args": ["-y", "@nanogpt/mcp"], "env": { "NANOGPT_API_KEY": "your_api_key_here", "NANOGPT_TIMEOUT_MS": "900000" } } } } ``` ### Node.js version compatibility The MCP server requires Node.js 22+: ```bash theme={null} node --version ``` ### MCP client connection checks * Re-add the server configuration if your client can’t connect. * Review your MCP client logs for connection errors. ## Getting Started 1. Get your API key from the [NanoGPT API dashboard](https://nano-gpt.com/api). 2. Configure your MCP client (like Claude Desktop) with the server settings. 3. Start chatting with your agent and ask it to "Search the web for..." or "Generate an image of...". For more details on specific models and pricing, visit the [Pricing Page](/api-reference/miscellaneous/pricing). # Model Suffixes Source: https://docs.nano-gpt.com/api-reference/miscellaneous/model-suffixes Supported suffixes for web search, memory, PII redaction, caching, reasoning visibility, thinking variants, and provider routing preferences. # Model Suffixes Append supported suffixes to the `model` value to request optional behavior for a single request. Most suffixes are stripped before the base model is routed. Model identity suffixes, such as supported `:thinking` variants, are preserved because they identify a distinct model or alias. Suffix parsing is case-insensitive for provider routing and provider suffixes. Avoid combining multiple suffixes that make conflicting provider-selection requests. ## Provider Routing Preference Suffixes These apply only to provider-selection-capable models. | Suffix | Meaning | | ------------- | ---------------------------------------------------------------------------------------- | | `:speed` | Pick the provider with the best estimated completion time, using TTFT plus TPS. | | `:fast` | Alias for `:speed`. | | `:throughput` | Pick the provider with the highest tokens per second. | | `:latency` | Pick the provider with the lowest time to first token. | | `:price` | Pick the cheapest provider by base input-plus-output token price. | | `:cheap` | Alias for `:price`. | | `:floor` | Alias for `:price`. | | `:tools` | Route to a tools-capable provider path for models that support tools provider selection. | | `:caching` | Require routing to a cache-capable provider, equivalent to top-level `caching: true`. | | `:cache` | Alias for `:caching`. | | `:cached` | Alias for `:caching`. | ```json theme={null} { "model": "zai-org/glm-5:fast", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "zai-org/glm-5:cheap", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "moonshotai/kimi-k2.6:thinking:caching", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "moonshotai/kimi-k2.6:tools", "messages": [{ "role": "user", "content": "Hello" }], "tools": [{ "type": "function", "function": { "name": "lookup", "parameters": { "type": "object", "properties": {} } } }] } ``` Rules: * Routing preference suffixes only consider user-selectable providers. Internal routing-only providers are excluded. * Routing preference suffixes are stripped before model mapping/routing. Non-routing identity suffixes such as `:thinking` are preserved. * Do not combine `:speed`, `:fast`, `:throughput`, `:latency`, `:price`, `:cheap`, or `:floor` with `X-Provider`, body `provider`, or a provider model suffix. * Do not combine `:tools` with a routing preference suffix, `X-Provider`, body `provider`, a provider model suffix, or `caching: true`. * Do not combine `:caching`, `:cache`, or `:cached` with `:tools`, `X-Provider`, body `provider`, a provider model suffix, or another routing preference suffix. * Routing preference requests are billed like explicit provider-selection requests. * Conflict error codes use the existing `speed_suffix_*` family for API compatibility, even when the suffix is not literally `:speed`. The caching suffixes are provider-capability routing, not prompt-cache annotation. They do not add Anthropic-style `cache_control` markers, configure cache TTLs, or force a cache write. For details, see [Prompt Caching](/api-reference/miscellaneous/prompt-caching#cache-capable-provider-routing). ## Provider Suffixes The API accepts a trailing provider suffix for public user-selectable provider IDs: ```json theme={null} { "model": "zai-org/glm-5:cerebras", "messages": [{ "role": "user", "content": "Hello" }] } ``` Recommended: use `X-Provider` for explicit provider overrides. The API also accepts a trailing provider suffix for user-selectable providers, such as `model-id:cerebras`, but `X-Provider` is clearer and easier to validate against provider-discovery responses. Provider suffixes must match a public user-selectable provider ID. They are case-insensitive, cannot be combined with routing preference suffixes or `:tools`, and are billed like explicit provider selection. ## Web Search Suffixes | Suffix | Meaning | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `:online` | Default web search, standard depth. For GPT-5+ / o-series models this may use OpenAI native web search unless a provider is explicit. | | `:online/linkup` | Linkup standard. | | `:online/linkup-deep` | Linkup deep. | | `:online/tavily` | Tavily standard. | | `:online/tavily-deep` | Tavily deep. | | `:online/brave` | Brave standard. | | `:online/brave-deep` | Brave deep. | | `:online/sofya` | Sofya search. Returns extracted page content; Sofya currently supports standard depth only. | | `:online/exa-fast` | Exa fast. | | `:online/exa-auto` | Exa auto. | | `:online/exa-neural` | Exa neural. | | `:online/exa-deep` | Exa deep. | | `:online/exa-deep-reasoning` | Exa deep-reasoning. | | `:online/exa-instant` | Exa instant. | | `:online/kagi` | Kagi standard search. | | `:online/kagi-web` | Kagi standard web. | | `:online/kagi-news` | Kagi standard news. | | `:online/kagi-search` | Kagi deep search. | | `:online/perplexity` | Perplexity standard. | | `:online/perplexity-deep` | Perplexity deep. | | `:online/valyu` | Valyu standard, all sources. | | `:online/valyu-deep` | Valyu deep, all sources. | | `:online/valyu-web` | Valyu standard, web only. | | `:online/valyu-web-deep` | Valyu deep, web only. | Web search suffixes can compose with memory and reasoning-exclude suffixes. If request body `webSearch.enabled` or legacy `linkup.enabled` is true, body configuration takes precedence over model suffix configuration. ## Memory Suffixes | Suffix | Meaning | | ---------------- | ------------------------------------------------------------ | | `:memory` | Enable Context Memory with default 30-day retention. | | `:memory-` | Enable Context Memory with retention clamped to 1..365 days. | Header `memory_expiration_days` takes precedence over `:memory-`. `memory: false` in the request body explicitly disables memory even if the model has a memory suffix. Memory can compose with web search, for example :online:memory-90. ## PII Redaction Suffixes | Suffix | Meaning | | --------------- | -------------------------------------- | | `:redaction` | Enable PII redaction for this request. | | `:redacted` | Alias for `:redaction`. | | `:piiredaction` | Alias for `:redaction`. | | `:piiredacted` | Alias for `:redaction`. | Redaction suffixes are stripped before model routing and can compose with other supported NanoGPT suffixes, such as `:online`. If a request explicitly enables redaction with a model suffix, redaction remains enabled even if an account-level or API-key default would otherwise be disabled for that request. For details, pricing, and limitations, see [PII Redaction](/api-reference/miscellaneous/pii-redaction). ## Reasoning and Thinking Suffixes | Suffix | Meaning | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `:thinking` | Model-specific thinking/reasoning variant when that exact ID or documented alias exists. | | `-thinking` | Legacy alias pattern for some model families only, not universal. | | `:` on Anthropic thinking IDs | Thinking budget suffix for mapped Anthropic thinking aliases, for example `claude-sonnet-4-thinking:8192`. | | `:reasoning-exclude` | Equivalent to `reasoning: { "exclude": true }`; hides reasoning fields/blocks and strips the suffix before routing. | `:reasoning-exclude` works on Chat Completions and Text Completions. It composes with other suffixes such as `:thinking`, `:online`, and `:memory`. `:thinking` is model identity, not provider routing, and is not universal. ## Official and Original Route Suffixes Some model families expose `:official` / `:original` aliases to force the official-provider route. These are model-specific; check the model page or provider-selection docs for supported IDs. ## Conflict Rules Do not combine conflicting routing directives: ```json theme={null} { "model": "zai-org/glm-5:fast:cerebras" } { "model": "zai-org/glm-5:cheap", "provider": "cerebras" } { "model": "zai-org/glm-5:tools:fast" } ``` ## Examples ```json theme={null} { "model": "zai-org/glm-5:fast", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "zai-org/glm-5:cheap", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "zai-org/glm-5:thinking:fast", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "moonshotai/kimi-k2.6:thinking:caching", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "openai/gpt-5.6-sol:online/exa-instant:memory-30", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "openai/gpt-5.6-sol:redaction:online", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "anthropic/claude-opus-4.6:memory-30:online/linkup-deep:reasoning-exclude", "messages": [{ "role": "user", "content": "Hello" }] } ``` # OAuth PKCE Source: https://docs.nano-gpt.com/api-reference/miscellaneous/oauth-pkce Let users sign in with NanoGPT and receive an app-specific API key through authorization-code PKCE. ## Overview NanoGPT supports authorization-code OAuth with PKCE for public clients. The returned credential is a dedicated NanoGPT API key in `sk-nano-...` format, scoped to the approved app/user grant. Use the returned key as a bearer token against the OpenAI-compatible API: ```http theme={null} Authorization: Bearer sk-nano-... ``` API base URL: ```text theme={null} https://nano-gpt.com/api/v1 ``` OAuth-created keys can spend from the user's NanoGPT balance, subject to account balance, subscriptions, API key settings, optional OAuth spend caps, expiration, allowed origins, and model/provider limits. Treat the returned `access_token` like a password. Do not log it, embed it in browser-visible HTML, or expose it to other users. ## Choose a Flow | Flow | Use when | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | Shortcut key handoff | Local apps, coding agents, chat frontends, and tools that want the shortest browser sign-in path | | Standard OAuth PKCE | Generic OAuth clients, persistent `client_id` registrations, and clients that rely on authorization server metadata | | Authenticated downstream key code | An already authenticated app needs to create a one-time authorization code for a downstream local app | All three flows return the same kind of credential: a dedicated NanoGPT API key. ## Discovery Authorization server metadata: ```http theme={null} GET /.well-known/oauth-authorization-server HTTP/1.1 Host: nano-gpt.com ``` Example response: ```json theme={null} { "issuer": "https://nano-gpt.com", "authorization_endpoint": "https://nano-gpt.com/oauth/authorize", "token_endpoint": "https://nano-gpt.com/oauth/token", "registration_endpoint": "https://nano-gpt.com/oauth/register", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["none"], "scopes_supported": ["models.read", "api.use"], "service_documentation": "https://nano-gpt.com/auth.md", "resource_documentation": "https://nano-gpt.com/api", "x-nanogpt-token-format": "sk-nano-api-key", "x-nanogpt-oauth-shortcut-authorization_endpoint": "https://nano-gpt.com/auth", "x-nanogpt-oauth-shortcut-token_endpoint": "https://nano-gpt.com/api/v1/auth/keys", "x-nanogpt-oauth-shortcut-code_endpoint": "https://nano-gpt.com/api/v1/auth/keys/code" } ``` Additional machine-facing metadata: | URL | Purpose | | ------------------------------------------- | --------------------------- | | `GET /auth.md` | Machine-facing auth guide | | `GET /.well-known/oauth-protected-resource` | Protected resource metadata | ## Scopes | Scope | Meaning | | ------------- | ------------------------------------------------------------------------------------------------------- | | `models.read` | Read public NanoGPT model catalogs and pricing metadata | | `api.use` | Use NanoGPT API endpoints with a user-approved API key, bounded by account balance and per-key settings | For this MVP, OAuth requests must include `api.use` because the returned access token is a spend-capable API key. Recommended scope: ```text theme={null} api.use models.read ``` NanoGPT normalizes returned scopes to its supported scope order: ```text theme={null} models.read api.use ``` ## PKCE Requirements NanoGPT requires S256 PKCE. * `code_challenge_method` must be `S256`. * `plain` is rejected. * `code_verifier` must use RFC 7636 characters: `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, `~`. * `code_verifier` length must be 43 to 128 characters. * `code_challenge` must be base64url-encoded SHA-256 of the verifier. * Authorization codes expire quickly and are one-time use. JavaScript helper: ```js theme={null} import { createHash, randomBytes } from "node:crypto"; const codeVerifier = randomBytes(64).toString("base64url"); const codeChallenge = createHash("sha256") .update(codeVerifier, "utf8") .digest("base64url"); ``` ## Redirect URI Rules Allowed redirect URIs: * HTTPS redirect URIs. * Loopback HTTP redirect URIs with an explicit port, such as `http://127.0.0.1:8787/callback`, `http://localhost:8787/callback`, or `http://[::1]:8787/callback`. Rejected redirect URIs: * Wildcards. * URL fragments. * Credentials in URLs. * Non-HTTPS web redirects. * Loopback HTTP without an explicit port. * Redirect URIs that do not exactly match the registered or callback URI. Loopback redirects are canonicalized to `127.0.0.1` internally. ## Shortcut Key Handoff Use this flow for local apps and clients that want the shortest browser sign-in integration. ### 1. Generate PKCE Generate: * `code_verifier` * `code_challenge = base64url(sha256(code_verifier))` * `state` ### 2. Redirect to NanoGPT Send the user to: ```http theme={null} GET https://nano-gpt.com/auth ``` Query parameters: | Parameter | Required | Description | | ----------------------- | ----------- | --------------------------------------------------------- | | `callback_url` | Yes | Callback URL. `redirect_uri` is accepted as an alias | | `code_challenge` | Yes | S256 PKCE challenge | | `code_challenge_method` | No | Must be `S256` if provided. Defaults to `S256` | | `scope` | No | Defaults to `api.use models.read`. Must include `api.use` | | `state` | Recommended | Opaque client state. NanoGPT passes it back unchanged | | `client_name` | No | Display name shown on the consent screen | | `app_name` | No | Alias for `client_name` | | `name` | No | Alias for `client_name` | | `title` | No | Alias for `client_name` | Example: ```text theme={null} https://nano-gpt.com/auth?callback_url=http%3A%2F%2F127.0.0.1%3A8787%2Fcallback&code_challenge=...&code_challenge_method=S256&scope=api.use%20models.read&state=...&client_name=My%20Local%20App ``` NanoGPT creates or reuses an internal callback client, then redirects the user into the consent flow. ### 3. Handle the Callback On approval: ```text theme={null} http://127.0.0.1:8787/callback?code=...&state=... ``` On denial or safe OAuth error: ```text theme={null} http://127.0.0.1:8787/callback?error=access_denied&state=... ``` If the callback URL is invalid, NanoGPT does not redirect to it and shows the error on NanoGPT instead. ### 4. Exchange the Code ```http theme={null} POST /api/v1/auth/keys HTTP/1.1 Host: nano-gpt.com Content-Type: application/json ``` JSON body: ```json theme={null} { "grant_type": "authorization_code", "code": "...", "code_verifier": "..." } ``` `grant_type` is optional. If present, it must be `authorization_code`. Form-encoded bodies are also accepted. Success response: ```json theme={null} { "key": "sk-nano-...", "access_token": "sk-nano-...", "token_type": "Bearer", "scope": "models.read api.use", "user_id": "..." } ``` Use either `key` or `access_token`; they contain the same value. ## Standard OAuth PKCE Use this flow for generic OAuth clients that want explicit dynamic registration and standard OAuth endpoint names. ### 1. Register the Client ```http theme={null} POST /oauth/register HTTP/1.1 Host: nano-gpt.com Content-Type: application/json ``` Request: ```json theme={null} { "client_name": "My Local App", "redirect_uris": ["http://127.0.0.1:8787/callback"], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "none", "client_uri": "https://example.com", "logo_uri": "https://example.com/logo.png" } ``` Required fields: * `client_name` * `redirect_uris` Optional fields: * `grant_types`, defaults to `["authorization_code"]` * `response_types`, defaults to `["code"]` * `token_endpoint_auth_method`, defaults to `none` * `client_uri`, must be HTTPS and have no fragment * `logo_uri`, must be HTTPS and have no fragment Only public PKCE clients are supported. NanoGPT does not issue client secrets. Success response: ```json theme={null} { "client_id": "ngpt_...", "client_name": "My Local App", "client_uri": "https://example.com/", "logo_uri": "https://example.com/logo.png", "redirect_uris": ["http://127.0.0.1:8787/callback"], "grant_types": ["authorization_code"], "response_types": ["code"], "token_endpoint_auth_method": "none" } ``` ### 2. Redirect to the Authorization Endpoint ```http theme={null} GET https://nano-gpt.com/oauth/authorize ``` Query parameters: | Parameter | Required | Description | | ----------------------- | -------- | ------------------------------------------------------------ | | `response_type` | Yes | Must be `code` | | `client_id` | Yes | Client ID from registration or a manually provisioned client | | `redirect_uri` | Yes | Exact registered redirect URI | | `scope` | Yes | Must include `api.use`. Recommended: `api.use models.read` | | `state` | Yes | Opaque client state | | `code_challenge` | Yes | S256 PKCE challenge | | `code_challenge_method` | Yes | Must be `S256` | | `prompt` | No | `consent` forces the consent screen | Example: ```text theme={null} https://nano-gpt.com/oauth/authorize?response_type=code&client_id=ngpt_...&redirect_uri=http%3A%2F%2F127.0.0.1%3A8787%2Fcallback&scope=api.use%20models.read&state=...&code_challenge=...&code_challenge_method=S256 ``` The user signs in to NanoGPT if needed and sees a consent screen showing the app name, redirect host, NanoGPT account, balance, requested scopes, spend warning, and optional daily, weekly, or monthly spend cap. For normal registered web clients, NanoGPT may auto-approve an unchanged active grant. For loopback and callback shortcut clients, the consent screen is shown again. ### 3. Handle the Callback On approval: ```text theme={null} https://app.example/callback?code=...&state=... ``` On denial: ```text theme={null} https://app.example/callback?error=access_denied&state=... ``` OAuth parameter validation errors are redirected only after the client and redirect URI have been validated. Invalid redirect URIs are shown as errors on NanoGPT and are not redirected. ### 4. Exchange the Code ```http theme={null} POST /oauth/token HTTP/1.1 Host: nano-gpt.com Content-Type: application/x-www-form-urlencoded ``` Form body: ```text theme={null} grant_type=authorization_code&client_id=ngpt_...&redirect_uri=http%3A%2F%2F127.0.0.1%3A8787%2Fcallback&code=...&code_verifier=... ``` Required fields: * `grant_type=authorization_code` * `client_id` * `redirect_uri` * `code` * `code_verifier` JSON bodies are also accepted. Success response: ```json theme={null} { "access_token": "sk-nano-...", "token_type": "Bearer", "scope": "models.read api.use" } ``` NanoGPT does not issue refresh tokens in this MVP. ## Authenticated Downstream Key Code An already authenticated app can create a one-time authorization code for a downstream local app. ```http theme={null} POST /api/v1/auth/keys/code HTTP/1.1 Host: nano-gpt.com Authorization: Bearer sk-nano-... Content-Type: application/json ``` Request: ```json theme={null} { "redirect_uri": "http://127.0.0.1:8787/callback", "code_challenge": "...", "code_challenge_method": "S256", "scope": "api.use models.read", "key_label": "Local coding agent", "limit": 20, "usage_limit_type": "monthly", "expires_at": "2026-12-31T23:59:59.000Z", "client_name": "Local coding agent" } ``` Required: * `redirect_uri` or `callback_url` * `code_challenge` * Authenticated source API key in the `Authorization` header Optional: * `code_challenge_method`, defaults to `S256` * `scope`, defaults to `api.use models.read` * `key_label` or `key_name` * `limit` * `usage_limit_type`: `daily`, `weekly`, or `monthly`; defaults to `monthly` when `limit` is present * `expires_at` * `client_name`, `app_name`, or `name` * `x-title` or `x-app-name` headers as fallback app names Success response: ```json theme={null} { "id": "...", "code": "...", "app_id": "ngpt_callback_...", "user_id": "...", "expires_at": "...", "data": { "id": "...", "code": "...", "app_id": "ngpt_callback_...", "user_id": "...", "expires_at": "..." } } ``` The downstream app exchanges this code at: ```http theme={null} POST /api/v1/auth/keys ``` Restrictions: * The source API key must be active. * The source API key must be linked to a signed-in NanoGPT account. * OAuth-issued API keys cannot create further OAuth key codes. * Source keys with existing spend, request, model, provider, origin, or redaction restrictions are rejected for this flow. * If the source key has an expiration, the downstream key cannot outlive it. ## Use the Returned Credential List models: ```bash theme={null} curl "https://nano-gpt.com/api/v1/models" \ -H "Authorization: Bearer sk-nano-..." ``` OpenAI-compatible chat: ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/chat/completions" \ -H "Authorization: Bearer sk-nano-..." \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4.1-nano", "messages": [ { "role": "user", "content": "Say hello from NanoGPT OAuth." } ] }' ``` Common API surfaces: * `GET /api/v1/models` * `GET /api/v1/image-models` * `GET /api/v1/video-models` * `GET /api/v1/audio-models` * `POST /api/v1/chat/completions` * `POST /api/v1/responses` * `POST /api/v1/messages` ## Token and Key Behavior The OAuth `access_token` is a dedicated NanoGPT API key in this MVP. Implications: * It is long-lived unless an expiration is set. * It can spend from the user's NanoGPT balance. * It should be stored like a password. * It should not be logged or exposed in browser-visible HTML. * It can be revoked by deleting or disabling the API key in NanoGPT settings. * The same active grant can reuse the same key when settings are unchanged. * New app-specific limits or expiration can force a new dedicated key. OAuth-created keys are visible as API keys and are named: ```text theme={null} OAuth: ``` Consent screens can let users set an optional daily, weekly, or monthly USD spend cap. A `$0` cap blocks paid spend for that app. Leaving the cap empty means no app-specific cap. Browser/web OAuth keys are tied to origins derived from the redirect URI when applicable. Loopback keys allow common loopback origins for the chosen callback port. ## Revocation NanoGPT does not expose an OAuth revocation endpoint in this MVP. Users revoke access by deleting, disabling, expiring, or limiting the generated API key in NanoGPT settings. Client behavior: * If a stored key starts returning `401`, discard it. * Ask the user to sign in again. * Do not keep retrying a revoked key. ## Errors OAuth JSON errors use this shape: ```json theme={null} { "error": "invalid_grant", "error_description": "authorization code expired" } ``` Common errors: | Endpoint | Error | Meaning | Recommended action | | ------------------------ | ---------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `/auth` | `invalid_request` | Callback URL or PKCE challenge is invalid | Rebuild the authorization URL | | `/oauth/register` | `invalid_request` | Client metadata or redirect URI is invalid | Fix registration request | | `/oauth/authorize` | `unsupported_response_type` | `response_type` is not `code` | Use authorization-code flow | | `/oauth/authorize` | `invalid_scope` | Scope is missing, unknown, or lacks `api.use` | Request `api.use models.read` | | `/oauth/token` | `invalid_grant` | Code is expired, already used, wrong client, wrong redirect URI, or PKCE failed | Restart OAuth | | `/api/v1/auth/keys` | `invalid_grant` | Code is invalid for this endpoint, expired, already used, or PKCE failed | Restart OAuth | | `/api/v1/auth/keys/code` | `invalid_request` | Source key or requested key policy is not eligible | Use an unrestricted user-created API key or normal OAuth | | API endpoints | `missing_api_key` | No bearer credential was sent | Ask user to sign in | | API endpoints | `invalid_api_key` | Key is invalid, inactive, expired, or blocked | Drop key and restart OAuth | | API endpoints | `api_key_origin_not_allowed` | Browser Origin is not allowed for that key | Ask user to update key settings or restart OAuth with the correct redirect origin | ## Rate Limits and Abuse Controls OAuth endpoints are rate-limited. Developers should: * Avoid repeated failed token exchanges. * Restart the flow after an `invalid_grant`. * Never reuse authorization codes. * Never log raw authorization codes, code verifiers, or API keys. ## Local App Example Example source: ```text theme={null} examples/oauth-local-app ``` Repository URL: ```text theme={null} https://github.com/Nano-GPT-com/nanogpt/tree/main/examples/oauth-local-app ``` Run: ```bash theme={null} node examples/oauth-local-app/oauth-local-app.mjs ``` Local development: ```bash theme={null} NANOGPT_BASE_URL=http://localhost:3000 node examples/oauth-local-app/oauth-local-app.mjs ``` The example starts a localhost callback server, generates PKCE, sends the user to `/auth`, exchanges the code at `/api/v1/auth/keys`, calls `/api/v1/models`, and masks the key in terminal output. # Partner Auth Source: https://docs.nano-gpt.com/api-reference/miscellaneous/partner-auth Add NanoGPT-powered AI to your product while keeping your own user accounts, brand, and UI. ## Overview Partner Auth lets your backend authenticate one of your users to NanoGPT without giving you direct access to NanoGPT account credentials or API keys. You send a short-lived JWT signed by your backend. NanoGPT verifies it, maps your user ID to a normal NanoGPT account behind the scenes, and runs requests against that linked account. The same partner user always maps to the same NanoGPT account. Partner Auth is available by request. Contact NanoGPT to configure your partner slug, JWT audience, public signing key, redirect allowlist, and optional commercial settings before production use. ## How It Works ```mermaid theme={null} flowchart TD A["Your app user"] --> B["Your backend"] B -->|"Signs short-lived JWT"| C["NanoGPT API"] C -->|"Verifies your public key"| D["Partner Auth"] D -->|"Maps your user ID"| E["Linked NanoGPT account"] E --> F["Balance, usage, models, and billing"] F --> C C --> B B --> A ``` Your backend owns the private key. NanoGPT stores only the public key. ## What NanoGPT Configures Before launch, NanoGPT configures: * Your partner slug, for example `example` * Your JWT audience, for example `nanogpt-partner-api:example` * Your public signing key and key ID, for example `example-2026-04` * Allowed browser redirect URLs for SSO * Optional referral revenue share * Optional partner tiers for discounts and request-access allowance rules You keep the private key in your own secret manager. ## JWT Requirements Every partner-authenticated request uses: ```http theme={null} Authorization: Bearer ``` JWT header: ```json theme={null} { "alg": "ES256", "kid": "example-2026-04" } ``` JWT claims: ```json theme={null} { "iss": "example", "aud": "nanogpt-partner-api:example", "sub": "your-opaque-user-id", "iat": 1770000000, "exp": 1770000300, "jti": "unique-request-id", "scope": ["request:create"] } ``` Rules: * Use `ES256` or `RS256`. * `exp - iat` must be at most 5 minutes. * `jti` must be unique per request token. * `sub` must be your stable user identifier, opaque to NanoGPT. * Do not send emails, names, or other personal data as `sub`. * NanoGPT stores `HMAC-SHA256(sub)`, not the raw `sub`. * Create JWTs on your backend only. Never sign tokens in the browser. Optional tier claim: ```json theme={null} { "tier": "premium" } ``` NanoGPT validates the tier against your active partner tier configuration. ## Scopes Use the smallest scope needed for each request. | Scope | Purpose | | ---------------- | ------------------------------------------------------------------ | | `request:create` | Create AI requests such as chat, image, video, or audio requests | | `session:web` | Create a one-time browser login link | | `balance:read` | Read the linked user's NanoGPT balance | | `deposit:create` | Create a deposit or top-up request | | `usage:read` | Read usage for the JWT `sub` | | `usage:read:any` | Backend-only scope to read usage for another `subject` query param | ## Send AI Requests Use a partner JWT instead of a NanoGPT API key. ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/chat/completions" \ -H "Authorization: Bearer $PARTNER_JWT" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.5", "messages": [ { "role": "user", "content": "Summarize this article in three bullets." } ] }' ``` The JWT must include: ```text theme={null} request:create ``` The request is charged to the linked NanoGPT account for the JWT `sub`. See also: [Chat Completion](/api-reference/endpoint/chat-completion), [Image Generation](/api-reference/image-generation), and [Video Generation](/api-reference/video-generation). ## Check User Balance ```bash theme={null} curl -X POST "https://nano-gpt.com/api/check-balance" \ -H "Authorization: Bearer $PARTNER_JWT" ``` The JWT must include: ```text theme={null} balance:read ``` The response includes the linked user's balance and deposit details. ## Initiate A Top-Up Partner-authenticated top-ups are not limited to Nano. The same linked NanoGPT account can be funded through enabled NanoGPT deposit methods, including: * Native Nano direct deposits: read the linked user's `nanoDepositAddress` from the balance response and show that address to the user. No deposit call is needed; incoming Nano credits automatically. See [Check User Balance](#check-user-balance). * Stablecoins and major chains (`usdc`, `usdt`, `sol`, `eth`, `base`, `arbitrum`, `polygon`, `bnb`, `trx`): call `POST /api/transaction/create/boomfi/{ticker}` to create a hosted payment for the linked user. * Crypto invoice or swap deposits (`btc`, `btc-ln`, `ltc`, `ltc-mweb`, `xmr`, `doge`, `dash`, `zec`): call `POST /api/transaction/create/{ticker}`. * Card deposits where enabled for the partner integration. Deposit creation, status polling, and cancellation all require the `deposit:create` scope on the partner JWT. Available methods can change by region, provider status, amount, and partner configuration. Ask NanoGPT which top-up methods should be enabled for your integration before launch. For invoice-style payment methods, create the payment request for the linked user with the ticker route: ```bash theme={null} curl -X POST "https://nano-gpt.com/api/transaction/create/btc-ln" \ -H "Authorization: Bearer $PARTNER_JWT" \ -H "Content-Type: application/json" \ -d '{"amount": 0.00001}' ``` The JWT must include: ```text theme={null} deposit:create ``` NanoGPT resolves the user from the JWT `sub` and returns deposit details for that linked account. For ticker-specific limits, request bodies, status polling, and supported payment methods, see [Crypto Deposits](/api-reference/endpoint/crypto-deposits). ## Browser SSO Use browser SSO when you want to send a user from your product into NanoGPT already signed in as the linked account. Create a one-time login link: ```bash theme={null} curl -X POST "https://nano-gpt.com/api/partners/auth/login-links" \ -H "Authorization: Bearer $PARTNER_JWT" \ -H "Content-Type: application/json" \ -d '{ "redirect_url": "https://your-product.example/account/ai" }' ``` The JWT must include: ```text theme={null} session:web ``` NanoGPT returns: ```json theme={null} { "url": "https://nano-gpt.com/auth/partner/example?token=...", "expiresAt": "2026-05-07T12:00:00.000Z" } ``` The login token is one-time use and short-lived. Absolute redirect URLs must be allowlisted. ## Read User Usage Read usage for the JWT `sub`: ```bash theme={null} curl "https://nano-gpt.com/api/partners/users/usage?duration=month" \ -H "Authorization: Bearer $PARTNER_JWT" ``` The JWT must include: ```text theme={null} usage:read ``` Backend service usage lookup for a specific user: ```bash theme={null} curl "https://nano-gpt.com/api/partners/users/usage?subject=your-opaque-user-id&duration=month" \ -H "Authorization: Bearer $PARTNER_SERVICE_JWT" ``` If `subject` differs from the JWT `sub`, the JWT must also include: ```text theme={null} usage:read:any ``` Use `usage:read:any` only from trusted backend services. ## Funding Options NanoGPT supports user-funded usage by default: * Each linked user has their own NanoGPT balance. * Your product can show the user's balance and top-up options. * If the linked user has no balance, paid requests return the normal insufficient-balance response. * If configured, your partner account can earn referral revenue from user-funded top-ups. Partner tiers can configure usage discounts once NanoGPT enables tiers for your integration: * `free`: no discount * `basic`: 5% discount * `premium`: 10% discount Tier names and discount rates are configurable per partner. ## Request-Access Funding Features Some funding options require NanoGPT approval and explicit configuration before they can be used: * Free starter prompts or free starter credits * Partner-funded daily, weekly, monthly, or one-time allowances * Custom per-tier discounts * Partner settlement for sponsored usage These options are not automatically available just because Partner Auth is enabled. Treat them as request-access features: describe the desired user plans, allowance amounts, discount rates, expected volume, and settlement model to NanoGPT before launch. Allowance rules are modeled for partner-funded usage: * one-time signup allowance * weekly allowance * monthly allowance Allowance settlement is not an active production payment source unless NanoGPT explicitly enables it for your integration. ## Error Handling Common responses: | Status | Meaning | | ------ | ------------------------------------------------------------------------------------------------------------ | | `401` | Missing, invalid, expired, replayed, or unauthorized partner JWT, including a JWT missing the required scope | | `402` | Linked user needs balance before the request can run | | `403` | Reading another user's usage with `subject` without the `usage:read:any` scope | | `429` | Too many requests or too many auth failures | | `500` | NanoGPT could not complete the server-side operation | Retry only when the error is transient, such as `429` or `500`. ## Security Checklist * Sign JWTs only on your backend. * Keep private keys in your secret manager. * Use short-lived JWTs, max 5 minutes. * Use a unique `jti` per request token. * Use opaque stable user IDs as `sub`. * Never put PII in `sub`. * Request only the scopes needed for the operation. * Do not expose `usage:read:any` or `deposit:create` from browser code. * Rotate keys periodically and revoke old keys after rollout. ## Minimal Backend Flow 1. User opens your AI feature. 2. Your backend signs a JWT with `sub = your user id` and the needed scope. 3. Your backend calls NanoGPT with `Authorization: Bearer `. 4. NanoGPT verifies the JWT and maps the user to a linked NanoGPT account. 5. NanoGPT runs the request, checks balance, applies configured partner discount, and records usage. 6. Your product renders the result to the user. # PII Redaction Source: https://docs.nano-gpt.com/api-reference/miscellaneous/pii-redaction Optional PII redaction for API and web chat requests. # PII Redaction NanoGPT supports optional PII redaction for API and web chat requests. When redaction is enabled, NanoGPT routes the request through Grepture before it reaches the selected model. Grepture masks supported personal information before the model sees the prompt, then restores masked values in the model response when possible. This is useful when you want to reduce the amount of personal or sensitive data sent to model providers while still getting a useful final answer. PII redaction is opt-in and costs **\$0.0005 per redacted request**. ## How Redaction Works When redaction is enabled: 1. The user sends a request to NanoGPT. 2. NanoGPT routes that request through Grepture. 3. Grepture detects supported personal information and replaces it with temporary placeholders before the request reaches the model. 4. The model processes the redacted prompt. 5. Grepture restores masked personal information in the response where possible. 6. NanoGPT returns the final response to the user. Simple flow: ```text theme={null} User -> NanoGPT -> Grepture -> Model -> Grepture -> NanoGPT -> User ``` Credential-like secrets are handled differently from normal PII. If the redaction layer detects API keys, tokens, private keys, webhooks, passwords, or similar secrets, those values are replaced with safe labels and are not restored into the response. This is intentional: a leaked credential should not be reinserted into model output. ## Supported API Endpoints PII redaction can be enabled on: * `POST /v1/chat/completions` * `POST /v1/completions` * `POST /v1/responses` ## Enable With API Headers Pass any one of these headers with a truthy value: | Header | Recommended | | -------------------- | ----------- | | `redaction: true` | Yes | | `redacted: true` | No | | `piiredaction: true` | No | | `piiredacted: true` | No | Truthy values accepted by the API: * `true` * `1` * `yes` * `on` Recommended header: ```http theme={null} redaction: true ``` ### Chat Completions Example ```bash theme={null} curl https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "redaction: true" \ -d '{ "model": "openai/gpt-5.5", "messages": [ { "role": "user", "content": "Email jane@example.com and tell her the appointment is tomorrow." } ] }' ``` ### Responses Example ```bash theme={null} curl https://nano-gpt.com/api/v1/responses \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "redaction: true" \ -d '{ "model": "openai/gpt-5.5", "input": "Summarize this customer note: John Smith called from 555-123-4567." }' ``` ## Disable For One Request If redaction is enabled by an account-level or API-key default, a request can opt out with any supported redaction header set to a falsy value: | Header | Recommended | | --------------------- | ----------- | | `redaction: false` | Yes | | `redacted: false` | No | | `piiredaction: false` | No | | `piiredacted: false` | No | Falsy values accepted by the API: * `false` * `0` * `no` * `off` Recommended opt-out header: ```http theme={null} redaction: false ``` If the same request explicitly enables redaction with a model suffix such as `:redaction`, redaction remains enabled. ## Enable With Model Suffixes You can also enable redaction by appending a redaction suffix to the model name: | Suffix | | --------------- | | `:redaction` | | `:redacted` | | `:piiredaction` | | `:piiredacted` | Example: ```json theme={null} { "model": "openai/gpt-5.5:redaction", "messages": [ { "role": "user", "content": "My phone number is 555-123-4567. Write a short reminder text." } ] } ``` NanoGPT strips the redaction suffix before sending the request onward, so the model is still resolved as the base model. The suffix is only used to turn redaction on for that request. Redaction suffixes can be combined with other supported NanoGPT model routing suffixes. For example: ```json theme={null} { "model": "openai/gpt-5.5:redaction:online", "messages": [ { "role": "user", "content": "Search for updates related to this customer domain: example.com" } ] } ``` For general suffix composition rules, see [Model Suffixes](/api-reference/miscellaneous/model-suffixes). ## Enable In Web Chat In the NanoGPT web app, users can enable PII redaction from: ```text theme={null} Settings > Privacy > PII redaction for chats ``` When this setting is enabled, web chat requests are routed through the redaction layer automatically. The same \$0.0005 per-request redaction charge applies. ## API Default Settings Users can enable redaction by default for API requests from: ```text theme={null} Settings > Privacy > PII redaction for API requests ``` When this account-level API setting is enabled, supported API text requests are routed through the redaction layer by default. Users can still opt out per request with `redaction: false`. API keys can also be configured individually from the API key settings modal: | API key setting | Behavior | | --------------- | --------------------------------------------------------------------- | | Inherit account | Use the account-level API default. | | Always redact | Redact requests made with this API key unless the request opts out. | | Never redact | Do not apply the account-level API redaction default to this API key. | Explicit per-request headers and model suffixes are still the clearest way to control redaction for a single request. ## What Gets Redacted The redaction layer is designed to detect and mask common personal information and sensitive credentials. ### Personal Information Supported PII categories: | Category | Description | | ------------- | ------------------------------------- | | Email | Email addresses | | Phone | Phone numbers | | SSN | US Social Security numbers | | Credit card | Payment card numbers | | IP address | IPv4 and IPv6-style network addresses | | Address | Physical addresses | | Name | Person names | | Date of birth | Birth dates and DOB-style values | For these PII categories, values are temporarily replaced with placeholders before reaching the model. When the response comes back, Grepture restores placeholders to the original values where possible. ### Secrets And Credentials NanoGPT also replaces common credential-like values with safe labels. These values are not restored. Examples of secret categories: | Category | Behavior | | ------------------------------------ | -------------------------- | | API keys and access tokens | Replaced with a safe label | | Cloud credentials and session tokens | Replaced with a safe label | | Webhook URLs | Replaced with a safe label | | Private key material | Replaced with a safe label | | Password or secret assignments | Replaced with a safe label | | Service account identifiers | Replaced with a safe label | | FTP-style embedded credentials | Replaced with a safe label | PII such as names, emails, and phone numbers is masked and restored. Credentials such as API keys, private keys, webhooks, and passwords are replaced permanently and are not restored into the model response. ## Pricing PII redaction costs **\$0.0005 per redacted request**. This charge is added only when redaction is enabled for the request. It is shown as a separate PII redaction add-on in usage details. The redaction charge is per request, not per token. ## Failure Behavior Redaction is fail-closed. If a request asks for redaction and the redaction layer cannot be used, NanoGPT does not silently retry the request without redaction. If redaction is enabled but the redaction service is unavailable, the request may fail rather than being sent without redaction. This prevents accidental unredacted fallback behavior. ## Limitations * Redaction is a privacy and safety layer, not a compliance guarantee. * Detection is best-effort and may not catch every possible form of personal information or secret. * PII restoration depends on the placeholder appearing in the model response. If the model rewrites, omits, or transforms the placeholder, restoration may not occur exactly as expected. * Redaction applies to request content that can be processed as text. It should not be treated as pixel-level image redaction, file sanitization, malware scanning, or document de-identification. * Users should still avoid sending unnecessary personal data or credentials to AI systems. ## FAQ ### Does redaction change the model I selected? No. Redaction changes the route the request takes before it reaches the model. It does not change the selected model. ### Does redaction work with streaming? Yes. Redaction is intended to work with streaming responses. The request is still routed through the redaction layer before the model sees it. ### Are redacted values restored in the final answer? For supported personal information categories, yes, where possible. For secrets and credentials, no. Secrets are replaced with safe labels and are not restored. ### Can I enable redaction globally for API requests? Yes. Users can enable an account-level API redaction default in `Settings > Privacy`. API keys can inherit that default, always redact, or opt out of the account default. Per-request headers and model suffixes are also supported. ### Can I opt out if my account or API key has redaction enabled by default? Yes. Send `redaction: false` on the request to opt out of account-level or API-key redaction defaults. Model suffixes such as `:redaction` explicitly enable redaction for that request. ### What happens if I use both the header and the model suffix? Redaction is enabled. There is still only one redaction charge for the request. # Pricing and Fees Source: https://docs.nano-gpt.com/api-reference/miscellaneous/pricing Information about API pricing # Pricing For full pricing per model see our [dedicated pricing page](https://nano-gpt.com/pricing) with all models listed. We do not charge any minimum fee per query, nor do we charge any fee on deposits. Pricing examples and model IDs follow the same canonical-ID policy as this documentation: use exact IDs returned by `GET /api/v1/models`, and do not rely on hidden/internal aliases. If you are (potentially) going to be a large user of our API reach out to us at [support@nano-gpt.com](mailto:support@nano-gpt.com) or our [Discord](https://discord.gg/KaQt8gPG6V) for a discount. # Prompt Caching Source: https://docs.nano-gpt.com/api-reference/miscellaneous/prompt-caching Understand NanoGPT caching behavior: implicit caching by default on supported providers (including many open-source routes), plus explicit prompt-caching controls for Claude. ## Overview Prompt caching lets you cache large, reusable prompt prefixes (system prompts, reference documents, tool definitions, and long conversation history) so follow-up requests can reuse the cached prefix instead of reprocessing it from scratch. Benefits: * Up to \~90% cost reduction on cached input tokens (cache hits) * Lower latency on requests with large static prefixes NanoGPT supports two caching modes: * **Implicit caching (default):** For providers/models that support provider-native prompt reuse (including OpenAI, Gemini, and many open-source provider routes), caching is applied automatically when eligible. No extra request fields are required. * **Explicit prompt caching (opt-in):** Claude models use explicit cache controls (`prompt_caching`/`promptCaching`/body-level `cache_control`, or inline `cache_control`) when you want deterministic cache boundaries and TTL control. NanoGPT also supports **cache-capable provider routing** with top-level `caching: true`. This is not a prompt annotation mode. It only requires routing to a provider that supports prompt/input caching and, by default, tries to keep later matching requests on the same provider. ## Supported Models ### Implicit caching (automatic) NanoGPT automatically uses implicit caching on providers/models that support it, including OpenAI and Gemini model families plus many open-source provider/model routes. No cache-control flags are required for this mode. For normal automatic routing, NanoGPT also tries cache-affinity routing when it can help: it hashes the request shape with the user/API-key or session identity and tries to route later matching requests to the same provider. This behavior is always active for eligible automatic routes; it is not controlled by the web UI prompt-caching toggle or by `prompt_caching`. If the provider that would be used does not support caching, NanoGPT does not apply cache-affinity routing for that request because there is no provider-side cache to reuse. NanoGPT also does not automatically add explicit cache-write annotations. For models/providers that only cache after explicit cache-control writes, such as Anthropic Claude and some Qwen routes, automatic affinity alone does not create cache hits. ### Cache-Capable Provider Routing Set top-level `caching: true` when you want NanoGPT to route the request to any available provider that supports prompt/input caching. This is capability-based routing: you do not need to choose a provider. If no cache-capable provider is available for the model, the request fails rather than silently using a non-caching provider. ```json theme={null} { "model": "model-id", "caching": true, "messages": [ { "role": "user", "content": "Hello" } ] } ``` By default, `caching: true` also enables sticky provider routing. After the first successful matching request, NanoGPT will try to use the same provider for later matching requests from the same API key or session, improving the chance of provider-side cache hits. This does not guarantee a cache hit. To require a cache-capable provider without stickiness: ```json theme={null} { "model": "model-id", "caching": true, "stickyprovider": false, "messages": [ { "role": "user", "content": "Hello" } ] } ``` Top-level `stickyProvider` is accepted as a camelCase alias for `stickyprovider`. You can request the same behavior with a model suffix: ```json theme={null} { "model": "moonshotai/kimi-k2.6:thinking:caching", "messages": [ { "role": "user", "content": "Hello" } ] } ``` The suffix aliases `:cache` and `:cached` are also accepted. Use `prompt_caching` / `promptCaching` only when you need provider-specific cache-control annotations or TTL behavior. Top-level `caching: true` does not add Anthropic-style `cache_control` markers or configure cache TTLs. ### Explicit prompt caching controls (Claude) Explicit prompt-caching controls are available on Claude models, including these families (examples): | Model family | Example model IDs | | -------------------- | ------------------------------------------------------- | | Claude 3.5 Sonnet v2 | `claude-3-5-sonnet-20241022` | | Claude 3.5 Haiku | `claude-3-5-haiku-20241022` | | Claude 3.7 Sonnet | `claude-3-7-sonnet-20250219` (and `:thinking` variants) | | Claude Sonnet 4 | `claude-sonnet-4-20250514` (and `:thinking` variants) | | Claude Sonnet 4.5 | `claude-sonnet-4-5-20250929` (and `:thinking` variants) | | Claude Haiku 4.5 | `claude-haiku-4-5-20251001` | | Claude Opus 4 | `claude-opus-4-20250514` (and `:thinking` variants) | | Claude Opus 4.1 | `claude-opus-4-1-20250805` (and `:thinking` variants) | | Claude Opus 4.5 | `claude-opus-4-5-20251101` (and `:thinking` variants) | | Claude Opus 4.6 | `claude-opus-4-6` (and `:thinking` variants) | All of the above are also supported via the `anthropic/` model prefix (for example `anthropic/claude-sonnet-4.5`, `anthropic/claude-opus-4.6:thinking`). ### Claude minimum cacheable tokens If your cached prefix is smaller than the minimum, the request still succeeds but no cache entry is created. | Model | Minimum cacheable tokens | | ------------------------------------------------------------------- | ------------------------ | | Claude Opus 4.5 | 4,096 | | Claude Haiku 4.5 | 4,096 | | Claude Sonnet 4.5, Sonnet 4, Opus 4, Opus 4.1, Opus 4.6, Sonnet 3.7 | 1,024 | | Claude Haiku 3.5 | 2,048 | ## How To Enable Explicit Prompt Caching (Claude) Prompt caching works on `POST /api/v1/chat/completions`. You can enable it in 3 ways. ### Option 1: body-level helper (`promptCaching` / `prompt_caching` / `cache_control`) Add a top-level helper object: ```json theme={null} { "model": "anthropic/claude-sonnet-4.5", "messages": [ { "role": "system", "content": "Your large static content..." }, { "role": "user", "content": "Summarize the key points." } ], "promptCaching": { "enabled": true, "ttl": "5m", "cutAfterMessageIndex": 0 } } ``` Parameters: | Parameter | Type | Default | Description | | -------------------------------------------------- | ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | | `enabled` | boolean | -- | Enable prompt caching | | `ttl` | `"5m"` or `"1h"` | `"5m"` | Cache time-to-live | | `cutAfterMessageIndex` / `cut_after_message_index` | integer | -- | Zero-based index; cache all messages up to and including this index | | `stickyProvider` | boolean | `false` | When `true`, avoid failover to preserve cache consistency (see [stickyProvider](#cache-consistency-with-stickyprovider)) | | `explicitCacheControl` / `explicit_cache_control` | boolean | `false` | When `true`, only refresh TTLs on existing inline `cache_control` blocks and do not auto-add cache breakpoints | **`explicitCacheControl`** *(boolean, default `false`)* When `true`, the system only refreshes TTLs on `cache_control` blocks you already placed in your request. No additional cache breakpoints are added automatically. This is useful when you use inline `cache_control` markers (Option 2) but also want body-level settings like `ttl` or `stickyProvider` to apply. Without this flag, the system may add its own cache breakpoints on top of yours. Also accepts the snake\_case alias `explicit_cache_control`. Aliases are accepted: * `promptCaching` * `prompt_caching` * `cache_control` (body-level helper alias) Passing `true` instead of an object defaults to: ```json theme={null} { "enabled": true, "ttl": "5m" } ``` If `cutAfterMessageIndex` is omitted, NanoGPT selects cache boundaries automatically. ### Option 2: inline `cache_control` markers Attach `cache_control` directly to content blocks you want cached: ```json theme={null} { "model": "anthropic/claude-sonnet-4.5", "messages": [ { "role": "system", "content": [ { "type": "text", "text": "Your long reference document...", "cache_control": { "type": "ephemeral" } } ] }, { "role": "user", "content": "Live question goes here" } ] } ``` ### Combining inline markers with body-level settings ```json theme={null} { "model": "anthropic/claude-sonnet-4.5", "messages": [ { "role": "system", "content": [ { "type": "text", "text": "You are a helpful coding assistant with access to a large codebase...", "cache_control": { "type": "ephemeral" } } ] }, { "role": "user", "content": "Summarize the auth module" } ], "promptCaching": { "enabled": true, "ttl": "1h", "explicitCacheControl": true } } ``` In this example, the system prompt's `cache_control` marker is preserved and its TTL is set to `1h`. The user message does not receive an auto-generated cache breakpoint. ### Option 3: `anthropic-beta` header (Claude-compatible) The Anthropic-compatible header is supported: ```text theme={null} anthropic-beta: prompt-caching-2024-07-31 ``` For Claude 1-hour TTL requests using Anthropic-native routing, also include: ```text theme={null} anthropic-beta: prompt-caching-2024-07-31,extended-cache-ttl-2025-04-11 ``` ## Controlling What Gets Cached > **Note:** `explicitCacheControl` and `cutAfterMessageIndex` serve different purposes. `cutAfterMessageIndex` tells the system where to auto-place cache breakpoints. `explicitCacheControl` tells the system not to auto-place any and only refresh what you already marked. If both are set, `explicitCacheControl` takes precedence. ### `cutAfterMessageIndex` Override automatic cache breakpoints by setting the last cached message index: ```json theme={null} { "promptCaching": { "enabled": true, "cutAfterMessageIndex": 4 } } ``` Messages at indices `0..4` are cached; later messages are not. You can also set this via request header: ```text theme={null} x-prompt-caching-cut-after: 4 ``` ### Cache block limit A maximum of 4 `cache_control` breakpoints are allowed per request (across system prompt, tools, and messages). If more are present, the oldest breakpoints are pruned automatically. ## Forcing a Cache Write There is no separate "force write" flag. Enable prompt caching and send the request. The first eligible request writes cache automatically (if provider thresholds/availability allow it). Repeated requests with the same cached prefix read from cache. ## Usage Fields (How To Verify Cache Hits) When caching is active (implicit or explicit), responses can include: * `cache_creation_input_tokens`: tokens written to cache on this request * `cache_read_input_tokens`: tokens read from cache on this request (cache hit when `> 0`) * `prompt_tokens_details.cached_tokens`: OpenAI-style cached token count Example: ```json theme={null} { "usage": { "prompt_tokens": 8500, "completion_tokens": 200, "cache_creation_input_tokens": 8000, "cache_read_input_tokens": 0 } } ``` When present, `x_nanogpt_pricing` includes cache pricing breakdown fields such as `cacheCreationInputTokens`, `cacheReadInputTokens`, `cacheTTL`, and `cacheCost`. For streaming requests, the final SSE chunk includes the same usage fields when usage is included. ## Pricing Cache writes and reads are billed differently by provider. Implicit-caching providers apply their cache pricing automatically when eligible. Explicit Claude caching uses the TTL settings below. ### Gemini Pro models (implicit caching, provider-native) | Token type | Rate (per 1M tokens) | Notes | | --------------------- | -------------------- | -------------------------- | | Regular input | \$2.00 | -- | | Cache write surcharge | +\$0.375 | Added on top of input cost | | Cache read | \$0.20 | 90% cheaper than input | Example: writing 10,000 cached tokens costs `(10k × $2.00/M) + (10k × $0.375/M) = $0.02375`. Reading 10,000 cached tokens costs `10k × $0.20/M = $0.002`. ### Gemini Flash models (implicit caching, provider-native) | Token type | Rate (per 1M tokens) | Notes | | --------------------- | -------------------- | -------------------------- | | Regular input | Varies by model | -- | | Cache write surcharge | +\$0.083 | Added on top of input cost | | Cache read | 10% of input rate | 90% cheaper than input | For Gemini 2.0 models, cache reads are 25% of the base input rate (75% cheaper), not 10%. ### Claude models (explicit caching) | TTL | Creation multiplier on cached input tokens | Read multiplier | | ---- | ------------------------------------------ | --------------- | | `5m` | `1.25x` | `0.1x` | | `1h` | `2.0x` | `0.1x` | ## TTL Options (Explicit Claude Controls) | TTL | Duration | Description | | ------ | --------- | --------------------------------------------------------------- | | `"5m"` | 5 minutes | Default. Suitable for interactive sessions. | | `"1h"` | 1 hour | Extended. Useful for batch processing or long-running sessions. | ## Structuring Prompts for Cache Hits Cache hits require the cached prefix to be byte-identical across requests. Best practices: * Put static content first (system prompt, reference docs, tool definitions). * Keep cached content identical across requests (no timestamps, request IDs, or dynamic inserts). * Put dynamic content after the cache boundary (typically the latest user message). ### Large tool catalogs and hosted search An eagerly loaded function catalog is part of the initial prompt prefix. Adding, removing, or changing any eager definition changes that prefix and can prevent reuse of the earlier cached prompt. On compatible Responses models, [hosted tool search](/api-reference/miscellaneous/hosted-tool-search) keeps a small search-tool definition in the initial prefix and reveals relevant functions later. This can reduce initial input tokens and improve the opportunity for cache reuse across catalog changes. It does not guarantee a cache hit: exact-prefix rules, minimum token thresholds, TTLs, and the selected model route still apply. Behavior: * On cache hit, TTL resets. * If TTL expires without reuse, cache expires. * Any prefix change (even one character) causes a cache miss and new cache creation. ## Cache Consistency with `stickyProvider` (Explicit Caching) Each provider keeps its own cache. If a request fails over to another provider, the previous cache may be unavailable. If cache consistency matters more than availability, set: ```json theme={null} { "promptCaching": { "enabled": true, "ttl": "5m", "stickyProvider": true } } ``` Behavior: * `stickyProvider: false` (default): the request may succeed even if routing changes, but you might rebuild caches. * `stickyProvider: true`: if a fallback would be required, the request returns `503` instead. ## NanoGPT Web UI In the NanoGPT web UI, models with explicit prompt-caching controls show a prompt caching toggle where you can choose cache duration. ## Limitations and Caveats * Provider-side minimum token thresholds still apply before a cache entry is created. * A maximum of 4 cache breakpoints (`cache_control`) are supported per request. * Some models report aggregate prompt usage differently; use `cache_creation_input_tokens` and `cache_read_input_tokens` for authoritative cached token counts. * On cache hits, a small non-zero `cache_creation_input_tokens` can appear due to per-request overhead and does not necessarily indicate a cache miss. * Implicit caching behavior (eligibility, TTL behavior, and exact discounts) is provider-dependent. # Provider Selection Source: https://docs.nano-gpt.com/api-reference/miscellaneous/provider-selection Choose the upstream provider for supported open-source models # Provider Selection Provider selection chooses the upstream provider for a supported model. It does not change the model ID. For example, to use Kimi K2.6 through Novita, keep the model as `moonshotai/kimi-k2.6`, or another supported alias for that model, and send the provider separately with `X-Provider`. Provider selection is optional; if you do nothing, the platform picks the provider. `X-Provider`, a body `provider` string, or a body `provider` object can select or constrain providers for a request. Explicit provider selection is always pay-as-you-go and is charged at the selected provider's price, including provider-selection markup. For subscription users, sending an explicit provider selection bypasses subscription coverage for that request. `X-Billing-Mode: paygo` is only needed when forcing pay-as-you-go billing without an explicit provider, or when saved provider preferences should apply to subscription-included traffic. See [Pay-As-You-Go Billing Override](/api-reference/miscellaneous/billing-override). This page intentionally documents only public provider IDs returned by the provider-discovery endpoints. Internal routing/provider names are never part of the public API contract. ## When Provider Selection Applies * Provider selection only applies when a model reports `supportsProviderSelection: true`. * Use `GET /api/models/:canonicalId/providers` to discover eligible models and provider IDs. * `supportsProviderSelection` is exposed on the model-specific provider endpoint. Do not rely on `/api/v1/models` or `/api/v1/models?detailed=true` to determine provider-selection support. * If a model does not support provider selection, the request ignores provider preferences. * You cannot currently force a provider and have that same request count as subscription-included usage. ## Discover Providers and Pricing Use this endpoint to list available providers and the pricing you will pay when selecting one. ``` GET /api/models/:canonicalId/providers ``` Provider rows may also include `distillationPolicy`, which indicates whether that specific hosted provider route is allowed for output-based model training or distillation under NanoGPT's recorded provider-terms and model-license rules. See [Distillation Policy](/api-reference/miscellaneous/distillation-policy). This endpoint is not under `/api/v1`. If your base URL is `https://nano-gpt.com/api/v1`, do not append this path to that base URL. Use: ```http theme={null} GET https://nano-gpt.com/api/models/moonshotai%2Fkimi-k2.6/providers ``` If the model ID contains `/`, URL-encode the slash when placing it in the path: ```http theme={null} GET https://nano-gpt.com/api/models/moonshotai%2Fkimi-k2.6/providers ``` Do not use the unencoded form, because the slash is treated as a path separator: ```http theme={null} GET https://nano-gpt.com/api/models/moonshotai/kimi-k2.6/providers ``` Here `:canonicalId` means the model ID or one of its supported aliases. Example response: ```json theme={null} { "canonicalId": "moonshotai/kimi-k2.6", "displayName": "Kimi K2.6", "supportsProviderSelection": true, "defaultPrice": { "inputPer1kTokens": 0.0005, "outputPer1kTokens": 0.0026 }, "providers": [ { "provider": "moonshot", "available": true }, { "provider": "novita", "available": true, "distillationPolicy": { "status": "allowed", "label": "License permits distillation", "basis": "permissive-open-weights", "sourceUrl": "https://example.com/license-or-terms", "note": "Provider terms do not record an output-training restriction, and the model-level policy allows distillation." } }, { "provider": "cloudflare", "available": true }, { "provider": "baseten", "available": true } ] } ``` Notes: * `defaultPrice` is used when you do not select a provider. * If `providers[].pricing` is present, it is what you pay when you select that provider (includes the markup). * Use the exact value from `providers[].provider` in the `X-Provider` header. * Provider-specific `distillationPolicy` can differ from the model-level policy. Explicit provider restrictions override model-level allowances. * If a model is unsupported, the response includes `supportsProviderSelection: false`. ## Per-Request Provider Override For a single request, send the provider ID in the `X-Provider` header. ```bash theme={null} curl https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "X-Provider: novita" \ -d '{ "model": "kimi-k2.6", "messages": [ { "role": "user", "content": "Hello" } ] }' ``` Notes: * `X-Provider` is case-insensitive (`x-provider` also works). * The provider ID must be one of the values returned by the providers endpoint. * The body `provider` field also accepts the same provider ID as a string on `POST /api/v1/chat/completions`, `POST /api/v1/completions`, and `POST /api/talk-to-gpt`. * Recommended: use `X-Provider` or body `provider` for explicit provider overrides. The API also accepts a trailing provider suffix for user-selectable providers, such as `model-id:cerebras`, but request fields are clearer and easier to validate against provider-discovery responses. * To avoid Moonshot for Kimi K2.6, select any available non-Moonshot provider ID returned by the provider-discovery endpoint, such as `novita`, `cloudflare`, `baseten`, or `inceptron`. * If the request would otherwise be subscription-covered, explicit provider selection still bypasses subscription coverage and bills the request as pay-as-you-go. You do not need to also send `billing_mode: "paygo"` or `X-Billing-Mode: paygo`. ## Provider Routing Object `POST /api/v1/chat/completions`, `POST /api/v1/completions`, and `POST /api/talk-to-gpt` also accept a structured `provider` object for per-request routing controls. ```json theme={null} { "model": "model-id", "provider": { "order": ["provider-a", "provider-b"], "only": ["provider-b"], "ignore": ["provider-c"], "sort": "throughput", "quantizations": ["fp8", "fp16"], "min_quantization": "fp8", "max_price": { "prompt": 0.5, "completion": 2 }, "allow_fallbacks": false, "require_parameters": true }, "messages": [ { "role": "user", "content": "Hello" } ] } ``` Provider entries can use public NanoGPT provider IDs or accepted display names/aliases. Use `GET /api/models/:canonicalId/providers` to discover public provider IDs for a model. Do not rely on internal provider names that are not returned by public provider-discovery responses. | Field | Type | Behavior | | ----------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `provider.order` | string\[] | Soft preference order. NanoGPT tries these providers first when possible, but may route elsewhere. Unknown providers are ignored. | | `provider.only` | string\[] | Hard provider pin. NanoGPT restricts routing to these providers and fails instead of routing outside the list. Unknown providers return `400` with `error.code: "provider_unknown_provider"`. | | `provider.ignore` | string\[] | Excludes providers from routing. Unknown providers are ignored. | | `provider.sort` | string | Routing preference. Supported values are `speed`, `throughput`, `latency`, `price`, `auto`, `none`, and `default`. `auto`, `none`, and `default` suppress stored/default routing preferences without requesting a sort. | | `provider.quantizations` | string\[] | Hard allowlist of model weight quantization levels. Supported values are `int4`, `fp4`, `fp6`, `int8`, `fp8`, `fp16`, `bf16`, `fp32`, and `unknown`. | | `provider.min_quantization` | string | Minimum precision floor. Supported values are `int4`, `fp4`, `fp6`, `int8`, `fp8`, `fp16`, `bf16`, and `fp32`. `unknown` is not allowed because it cannot be compared to a precision floor. | | `provider.max_price` | object | Optional `prompt` and/or `completion` price caps in USD per 1 million tokens. Pricing checks are best-effort when provider pricing is unknown. | | `provider.allow_fallbacks` | boolean | When `false`, disables cross-provider fallback for this request. | | `provider.require_parameters` | boolean | When `true`, requires the selected provider to support the requested parameters on routes that support parameter-level capability checks. | Examples: ```json theme={null} { "provider": { "order": ["provider-a", "provider-b"] } } ``` ```json theme={null} { "provider": { "only": ["provider-b"] } } ``` ```json theme={null} { "provider": { "ignore": ["provider-c"] } } ``` ```json theme={null} { "provider": { "quantizations": ["fp8"] } } ``` ```json theme={null} { "provider": { "min_quantization": "fp8" } } ``` ```json theme={null} { "provider": { "max_price": { "prompt": 0.5, "completion": 2 } } } ``` ```json theme={null} { "provider": { "require_parameters": true } } ``` ```json theme={null} { "provider": { "order": ["provider-b"], "allow_fallbacks": false } } ``` ### Routing Semantics * `order` is soft. Use `only` when the request must fail instead of using any provider outside the list. * `only` is hard. It restricts routing to the listed providers and disables internal fallback outside the pinned set. * `ignore` and `max_price` can resolve to a concrete provider. For direct provider routing, NanoGPT cannot represent "any provider except X" as a multi-provider pool. If filters like `ignore` or `max_price` are provided without `order` or `only`, NanoGPT resolves to one cheapest concrete provider that satisfies those filters. That is treated as explicit provider selection for billing and routing because the request changed the provider candidate set. * If `max_price` is provided without `order` or an explicit `sort`, NanoGPT treats it as a request for price-aware routing among providers that satisfy the cap. * Quantization filters are hard routing constraints. If no available provider satisfies the requested quantization, provider list, price, fallback, caching, and parameter constraints, the request may fail with a provider-availability error. * Per-request `provider` object controls take precedence over saved provider preferences for that request. They are not merged with saved preferences. ### Quantization Filters Use quantization filters when you need to avoid lower-precision provider routes for a model. `provider.quantizations` is an exact allowlist. It must be a non-empty array: ```json theme={null} { "model": "deepseek/deepseek-v3-0324", "messages": [ { "role": "user", "content": "Hello" } ], "provider": { "quantizations": ["fp8"] } } ``` When an exact quantization filter is supplied, providers without matching known metadata are excluded. To allow providers whose quantization metadata is not known, include `unknown` explicitly: ```json theme={null} { "provider": { "quantizations": ["fp8", "unknown"] } } ``` `provider.min_quantization` sets a minimum precision floor: ```json theme={null} { "model": "deepseek/deepseek-v3-0324", "messages": [ { "role": "user", "content": "Hello" } ], "provider": { "min_quantization": "fp8" } } ``` Minimum quantization is ordered by bit width. For example, `min_quantization: "fp8"` allows `int8`, `fp8`, `fp16`, `bf16`, and `fp32`. Integer and floating-point formats with the same bit width are treated as equivalent for minimum filtering. You may combine exact and minimum filters. NanoGPT uses the overlap: ```json theme={null} { "provider": { "quantizations": ["fp8", "fp16"], "min_quantization": "fp8" } } ``` If the filters do not overlap, the request is rejected as invalid. For example, `quantizations: ["fp4"]` with `min_quantization: "fp8"` is invalid because `fp4` is below the requested minimum. Use NanoGPT's canonical quantization values in API requests. Provider metadata may use different internal labels, but request parameters must use the supported canonical values listed above. ### Validation Invalid object shapes return a structured `400` error: * `provider.order`, `provider.only`, and `provider.ignore` must be arrays of strings. * `provider.quantizations` must be a non-empty array of supported quantization strings. * `provider.min_quantization` must be a supported comparable quantization string. `unknown` is only valid in `provider.quantizations`. * If `provider.quantizations` and `provider.min_quantization` have no overlap, the request is invalid. * Unknown providers in `only` return `provider_unknown_provider`. * Unknown providers in `order` and `ignore` are ignored. * `provider.max_price.prompt` and `provider.max_price.completion` must be non-negative numbers. * `provider.allow_fallbacks` and `provider.require_parameters` must be booleans. Example error: ```json theme={null} { "error": { "message": "Unknown or unavailable provider id in provider.only: not-a-provider", "type": "invalid_request_error", "param": "provider.only", "code": "provider_unknown_provider" } } ``` ## Consecutive Prompts and Prompt Caching When you explicitly select a provider with `X-Provider` or body `provider`, NanoGPT routes that request to the selected provider. For consecutive prompts, using the same provider helps keep routing stable, which is the setup you want for provider-side prompt-cache reuse when that provider and model support caching. For capability-based routing, use top-level `caching: true` or the `:caching` model suffix instead of choosing a specific provider. NanoGPT will route to any available provider that supports prompt/input caching for the requested provider-selection model, and it defaults to sticky provider routing so later matching requests prefer the same provider. General automatic routing may use cache-affinity routing for eligible cache-capable providers, but it does not require a cache-capable provider and may still choose different upstream providers when request shape, provider availability, or routing constraints change. For cache-sensitive workflows, either consistently send the same `X-Provider` value or use `caching: true` / `:caching` when any cache-capable provider is acceptable. ## Cache-Capable Provider Routing Set `caching: true` when you want NanoGPT to route a chat completion request to any available provider that supports prompt/input caching. This is capability-based provider selection, not explicit provider selection and not prompt-cache annotation. It does not add Anthropic-style `cache_control` markers or configure cache TTLs. ```json theme={null} { "model": "model-id", "caching": true, "messages": [ { "role": "user", "content": "Hello" } ] } ``` If no usable cache-capable provider exists for the model, the request fails instead of falling back to a non-cache-capable provider. By default, `caching: true` is sticky. The first successful matching request records the selected provider by API key or session. Later matching requests prefer that same provider when it is still usable, improving the chance of provider-side cache hits. NanoGPT does not guarantee that the request will be served from cache. To require a cache-capable provider without stickiness: ```json theme={null} { "model": "model-id", "caching": true, "stickyprovider": false, "messages": [ { "role": "user", "content": "Hello" } ] } ``` Top-level `stickyProvider` is accepted as a camelCase alias for `stickyprovider`. The model suffixes `:caching`, `:cache`, and `:cached` request the same cache-capable provider routing: ```json theme={null} { "model": "moonshotai/kimi-k2.6:thinking:caching", "messages": [ { "role": "user", "content": "Hello" } ] } ``` Routing order for `caching: true`: 1. Filter to providers that are available, not excluded by preferences, and marked as prompt-caching capable. 2. If stickiness is enabled, prefer the previously recorded provider for the same cache-relevant request shape when still usable. 3. Otherwise choose the cheapest cache-capable provider by base input + output price. 4. Use cache write/read pricing only as tie-breakers. ## Per-Request Routing Preference For provider-selection-capable models, you can append a routing preference suffix to the model ID: * `:speed` / `:fast`: best estimated completion time using TTFT plus TPS. * `:throughput`: highest tokens per second. * `:latency`: lowest time to first token. * `:price` / `:cheap`: lowest base input-plus-output token price. * `:floor`: alias for `:price`. * `:tools`: route to a tools-capable provider path when the model supports tools provider selection. Example: ```json theme={null} { "model": "zai-org/glm-5:fast", "messages": [{ "role": "user", "content": "Hello" }] } ``` Notes: * Routing preference suffixes are billed like explicit provider-selection requests. * They cannot be combined with `X-Provider`, body `provider`, or provider model suffixes. * `:tools` cannot be combined with routing preference suffixes or `caching: true`. * If the model does not support provider selection, the request returns an invalid request error for these suffixes. See [Model Suffixes](/api-reference/miscellaneous/model-suffixes) for the complete suffix reference and composition rules. ## Persistent Provider Preferences These endpoints let a user save provider preferences in their session metadata. ``` GET /api/user/provider-preferences PATCH /api/user/provider-preferences DELETE /api/user/provider-preferences ``` These endpoints require a logged-in web session. API-key-only requests should use per-request provider selection with `X-Provider`, unless preferences were already saved on the associated user session. Saved provider preferences apply to pay-as-you-go traffic. For subscription-included traffic, send `X-Billing-Mode: paygo` or `billing_mode: "paygo"` when you want saved provider preferences to apply. Example `GET` response (placeholders): ```json theme={null} { "preferredProviders": ["provider-a", "provider-b"], "excludedProviders": ["provider-c"], "enableFallback": true, "modelOverrides": { "model-id": { "preferredProviders": ["provider-b"], "enableFallback": false } }, "availableProviders": ["provider-a", "provider-b", "provider-c"] } ``` Example `PATCH` payload: ```json theme={null} { "preferredProviders": ["provider-a", "provider-b"], "excludedProviders": ["provider-c"], "enableFallback": false, "modelOverrides": { "model-id": { "preferredProviders": ["provider-b"], "enableFallback": true } } } ``` Field details: * `preferredProviders`: ordered list of allowed providers; the system tries each in order. * `excludedProviders`: providers that should never be used. * `enableFallback`: when `true` (default), fall back to the platform default if no preferred provider is available. * `modelOverrides`: optional per-model overrides for the fields above. * `availableProviders`: full set of provider IDs available to your account for the model. ## Resolution Order When `caching: true` is set, use the routing order in [Cache-Capable Provider Routing](#cache-capable-provider-routing). Otherwise, when multiple selections exist, the system resolves providers in this order: 1. Per-request explicit provider selection (`X-Provider`, body `provider` string/object, or provider suffix) 2. Per-request routing preference suffix (`:fast`, `:cheap`, etc.) 3. Per-model `preferredProviders` 4. Global `preferredProviders` 5. Platform default (only if `enableFallback` is true) ## Billing and Markup * If you explicitly select or constrain providers with `X-Provider` or body `provider`, billing uses the resolved provider-specific price plus a 5% markup and the request is treated as pay-as-you-go. * If saved preferences select a provider on pay-as-you-go traffic, billing uses the provider-specific price plus a 5% markup. * If you do not select a provider, billing uses the model's default price. ## Error Behavior * If `enableFallback` is `false` and no preferred provider is available, `/api/v1/chat/completions` returns `400` with `error.code: "no_fallback_available"`. * Invalid body `provider` objects return `400` with `error.type: "invalid_request_error"`. * `PATCH /api/user/provider-preferences` validates provider IDs and returns: * `422 INVALID_INPUT` for malformed payloads. * `400 INVALID_EXCLUSIONS` if exclusions would leave a model with no usable provider. # Rate Limits Source: https://docs.nano-gpt.com/api-reference/miscellaneous/rate-limits Information about API rate limits # Rate Limits NanoGPT enforces a mix of global throughput limits and (optionally) per-key daily limits. ## Global Throughput Limits (Per-Second) The NanoGPT API currently imposes a limit of **25 requests per second**. If you need a higher limit, email [support@nano-gpt.com](mailto:support@nano-gpt.com) with: * which model(s) you need * your target RPS / peak traffic patterns * whether requests are streaming If your use case requires more than **1 billion tokens/day** of a specific model, contact support. We can usually support it, and may be able to offer better pricing. ## Per-Key Daily Limits (Optional) NanoGPT supports **per-API-key daily limits** to help you cap usage and spend. You can set these when creating or editing an API key in the NanoGPT dashboard. Available limits: | Limit | Description | Reset | | ---------------------- | -------------------------------------------------------------------------- | ------------ | | Requests per Day (RPD) | Maximum number of API requests the key can make per day (across endpoints) | Midnight UTC | | USD per Day | Maximum estimated USD spend the key can incur per day | Midnight UTC | Notes: * Limits are enforced **per API key**, not per user. Multiple keys have independent counters. * If a limit is not set, that dimension is unlimited for the key. * Counters reset at **00:00 UTC** every day. * USD/day uses **cost estimation at request time**. Actual cost can differ slightly, so the effective cap is approximate. * Subscription-covered requests (models included in your subscription plan) can bypass the USD/day limit since no balance is spent, but they still count toward RPD. ## When A Daily Limit Is Exceeded When a per-key daily limit is exceeded, NanoGPT returns an **OpenAI-compatible** `429` error and includes a `Retry-After` header (seconds until the next reset at midnight UTC). Example response: ```json theme={null} { "error": { "message": "Daily request limit exceeded (1000/1000). Resets at midnight UTC.", "code": "daily_rpd_limit_exceeded", "type": "rate_limit_error" } } ``` Error codes: | Code | Description | | -------------------------- | --------------------------- | | `daily_rpd_limit_exceeded` | Request count limit reached | | `daily_usd_limit_exceeded` | Spend limit reached | # Compressed Request Bodies Source: https://docs.nano-gpt.com/api-reference/miscellaneous/request-compression Send gzip, deflate, or Brotli compressed JSON to the text APIs for faster uploads # Compressed Request Bodies The text generation APIs accept compressed JSON request bodies. Compress your payload, add a `Content-Encoding` header, and everything else works exactly as before — same request schema, same response. ``` Content-Encoding: gzip ``` ## Why compress? Chat requests resend the full conversation history every turn, so request bodies grow with the conversation — multi-hundred-kilobyte payloads are common for agents and long chats, and tool schemas add more. JSON like this compresses roughly **5:1 with gzip**. The win is mostly **your own latency**: the request body has to finish uploading before we can start model dispatch, and a large body costs multiple network round-trips just for TCP to ramp up. Compressing it: * **Cuts time-to-first-token**, most noticeably on long conversations, on high-latency routes, and on constrained uplinks. The further you are from the origin and the bigger your payloads, the more you save on every single request. * **Reduces your egress bandwidth** — relevant for server-to-server integrations that send us high volumes. * **Makes retries cheaper and uploads more robust** on flaky networks: fewer bytes in flight, fewer mid-upload stalls. If your bodies are small (a few KB), compression won't hurt but also won't buy you much — it matters once conversations get long. ## Supported endpoints and encodings | | | | -------------- | ------------------------------------------------------------------------------------------------------------ | | Endpoints | `POST /v1/chat/completions` · `POST /v1/responses` · `POST /v1/messages` | | Encodings | `gzip` (also `x-gzip`), `deflate` (zlib-wrapped or raw), `br` (Brotli) | | Content-Type | Must be JSON (`application/json`, `text/json`, or `*+json`) | | Authentication | Required — compressed bodies are only decompressed for authenticated requests, so send your API key as usual | | Size limit | 32 MB, enforced on both the compressed and the decompressed body | A comma-separated `Content-Encoding` list (e.g. `gzip, identity`) is accepted and decoded in reverse order per the HTTP spec, but a single encoding is all you need. ## Examples The OpenAI and Anthropic SDKs don't compress request bodies on their own, but both let you plug in a custom HTTP transport. The snippets below work with your existing SDK setup — or with plain `fetch`/`requests` if you don't use an SDK. ### curl ```bash theme={null} echo '{"model":"openai/gpt-5.6-sol","messages":[{"role":"user","content":"Hello!"}]}' \ | gzip \ | curl https://nano-gpt.com/api/v1/chat/completions \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -H "Content-Encoding: gzip" \ --data-binary @- ``` ### Python ```python requests theme={null} import gzip import json import requests payload = { "model": "openai/gpt-5.6-sol", "messages": [{"role": "user", "content": "Hello!"}], } response = requests.post( "https://nano-gpt.com/api/v1/chat/completions", data=gzip.compress(json.dumps(payload).encode("utf-8")), headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "Content-Encoding": "gzip", }, ) ``` ```python OpenAI SDK theme={null} import gzip import httpx from openai import OpenAI class GzipJsonTransport(httpx.BaseTransport): """Compress outgoing JSON request bodies with gzip.""" def __init__(self, inner: httpx.BaseTransport | None = None): self._inner = inner or httpx.HTTPTransport() def handle_request(self, request: httpx.Request) -> httpx.Response: body = request.read() content_type = request.headers.get("Content-Type", "") if ( body and "json" in content_type and "Content-Encoding" not in request.headers ): headers = dict(request.headers) headers.pop("content-length", None) headers["content-encoding"] = "gzip" request = httpx.Request( request.method, request.url, headers=headers, content=gzip.compress(body), ) return self._inner.handle_request(request) client = OpenAI( base_url="https://nano-gpt.com/api/v1", api_key=API_KEY, http_client=httpx.Client(transport=GzipJsonTransport()), ) completion = client.chat.completions.create( model="openai/gpt-5.6-sol", messages=[{"role": "user", "content": "Hello!"}], ) ``` ### JavaScript / TypeScript ```javascript fetch theme={null} import { gzipSync } from "node:zlib"; const payload = { model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Hello!" }], }; const response = await fetch("https://nano-gpt.com/api/v1/chat/completions", { method: "POST", headers: { Authorization: `Bearer ${process.env.NANOGPT_API_KEY}`, "Content-Type": "application/json", "Content-Encoding": "gzip", }, body: gzipSync(Buffer.from(JSON.stringify(payload))), }); ``` ```javascript OpenAI SDK theme={null} import { gzipSync } from "node:zlib"; import OpenAI from "openai"; // Compress JSON bodies before they leave the process. const gzipFetch = async (url, init = {}) => { const headers = new Headers(init.headers); const contentType = headers.get("content-type") ?? ""; if ( typeof init.body === "string" && contentType.includes("json") && !headers.has("content-encoding") ) { headers.set("content-encoding", "gzip"); return fetch(url, { ...init, headers, body: gzipSync(Buffer.from(init.body)) }); } return fetch(url, init); }; const client = new OpenAI({ baseURL: "https://nano-gpt.com/api/v1", apiKey: process.env.NANOGPT_API_KEY, fetch: gzipFetch, }); const completion = await client.chat.completions.create({ model: "openai/gpt-5.6-sol", messages: [{ role: "user", content: "Hello!" }], }); ``` The same transport works for `/v1/responses` and (with the Anthropic SDK's equivalent custom-fetch option) `/v1/messages` — the compression handling is identical on all three endpoints. ## Errors | Status | Code | Meaning | | ------ | ------------------------------ | --------------------------------------------------------- | | `415` | `unsupported_content_encoding` | `Content-Encoding` value we don't support (e.g. `zstd`) | | `415` | `unsupported_media_type` | Compressed body without a JSON `Content-Type` | | `401` | `authentication_error` | Compressed body on an unauthenticated request | | `413` | — | Body exceeds the 32 MB limit (compressed or decompressed) | Everything else — validation, billing, streaming — behaves exactly as with uncompressed requests. # Streaming Protocol (SSE) Source: https://docs.nano-gpt.com/api-reference/miscellaneous/streaming-protocol How NanoGPT streams responses over Server-Sent Events across chat completions, messages, and responses. ## Overview NanoGPT supports streaming responses via Server-Sent Events (SSE). Streaming is available on these endpoints: | Endpoint | Style | Content-Type | | --------------------------- | ---------------------------------- | -------------------------------------- | | `POST /v1/chat/completions` | OpenAI Chat Completions compatible | `text/event-stream` | | `POST /v1/messages` | Anthropic Messages compatible | `text/event-stream` (named SSE events) | | `POST /v1/responses` | OpenAI Responses API compatible | `text/event-stream` | All SSE streams are delivered as a sequence of `data:` frames separated by a blank line. Some endpoints also include an `event:` line to name the event type. ## Enabling Streaming Set `"stream": true` in the JSON request body. Chat Completions: ```json theme={null} { "model": "openai/gpt-5.6-sol", "messages": [{ "role": "user", "content": "Hello!" }], "stream": true } ``` Messages: ```json theme={null} { "model": "claude-sonnet-4-5-20250929", "max_tokens": 1024, "messages": [{ "role": "user", "content": "Hello!" }], "stream": true } ``` Responses: ```json theme={null} { "model": "openai/gpt-5.6-sol", "input": "Hello!", "stream": true } ``` If `stream` is omitted or `false`, the endpoint returns a single JSON response. ## Chat Completions Streaming (`/v1/chat/completions`) Chat Completions streams OpenAI-style `chat.completion.chunk` objects. ### Frame Format Each SSE frame is a JSON object in a `data:` line: ```text theme={null} data: {"id":"chatcmpl_...","object":"chat.completion.chunk","created":1700000000,"model":"...","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}]} ``` The first chunk often includes `delta.role: "assistant"`. Subsequent chunks typically include only incremental deltas like `delta.content`. ### Finish Reasons The final chunk has a non-null `finish_reason`: * `stop` * `length` * `tool_calls` * `content_filter` ### End of Stream After the final chunk, the stream terminates with: ```text theme={null} data: [DONE] ``` Clients should treat `[DONE]` as a literal string (do not JSON-parse it). ### Reasoning / Thinking Deltas Some models stream reasoning alongside content. Depending on the endpoint variant you use, the delta field may be `reasoning` or `reasoning_content`. Example: ```text theme={null} data: {"choices":[{"index":0,"delta":{"reasoning":"Thinking..."},"finish_reason":null}]} data: {"choices":[{"index":0,"delta":{"content":"Answer text..."},"finish_reason":null}]} ``` ### Tool Call Deltas Tool calls stream via `delta.tool_calls[]`. Accumulate `function.arguments` across frames for the same `tool_calls[index]`. ### Usage In Streaming Usage is not included by default in streaming. To receive usage, set: ```json theme={null} { "stream": true, "stream_options": { "include_usage": true } } ``` If enabled, the **final chunk** includes a `usage` field. (Some features, like prompt caching helpers, can cause usage to be included automatically.) `usage` can include provider-dependent fields beyond basic token counters, including nested details and cache fields: * `prompt_tokens` * `completion_tokens` * `total_tokens` * `prompt_tokens_details.cached_tokens` * `prompt_tokens_details.audio_tokens` * `completion_tokens_details.reasoning_tokens` * `completion_tokens_details.audio_tokens` * `completion_tokens_details.accepted_prediction_tokens` * `completion_tokens_details.rejected_prediction_tokens` * `reasoning_tokens` * `citation_tokens` * `num_search_queries` * `cache_creation_input_tokens` * `cache_read_input_tokens` * `input_tokens` When provider usage is missing or zero, NanoGPT may backfill final usage counts from pricing metadata (`x_nanogpt_pricing.inputTokens` / `x_nanogpt_pricing.outputTokens`) for consistency. ### `x_nanogpt_pricing` In Streaming `x_nanogpt_pricing` is an extension object: * In streaming: appears on the **final chunk only** * In non-stream JSON responses: appears as a top-level field Stable/core fields: * `amount?: number` * `currency?: string` * `error?: { status?: number; message: string }` (`message` is sanitized) Common optional fields: * `cost?: number` * `paymentSource?: string` * `inputTokens?: number` * `outputTokens?: number` * `cacheCost?: number` * `billedToTeam?: boolean` * `billedTeamId?: number | null` * `billedTeamName?: string | null` Compatibility rule: clients must tolerate additional fields and ignore unknown keys. Example final chunk: ```json theme={null} { "id": "chatcmpl_x", "object": "chat.completion.chunk", "created": 1772346058, "model": "gpt-5.1", "choices": [{ "index": 0, "delta": {}, "finish_reason": "stop" }], "usage": { "prompt_tokens": 123, "completion_tokens": 20, "total_tokens": 143, "prompt_tokens_details": { "cached_tokens": 123, "audio_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 12, "audio_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 }, "cache_creation_input_tokens": 123, "cache_read_input_tokens": 123 }, "x_nanogpt_pricing": { "cost": 0.000169, "inputTokens": 9, "outputTokens": 20, "cacheCost": 0, "paymentSource": "USD", "billedToTeam": false, "billedTeamId": null, "billedTeamName": null } } ``` ## Messages Streaming (`/v1/messages`) The Messages endpoint streams Anthropic-style **named SSE events**. Each event includes an `event:` line and a `data:` line. Typical sequence: 1. `message_start` 2. `content_block_start` 3. `content_block_delta` (repeated) 4. `content_block_stop` 5. `message_delta` 6. `message_stop` Example (end of stream): ```text theme={null} event: message_stop data: {"type":"message_stop"} ``` ### Tool Use Tool calls appear as `tool_use` content blocks. Tool input streams as `input_json_delta` fragments inside `content_block_delta` events. ### Thinking Blocks Thinking can appear as a separate content block type (`thinking`) before normal text blocks. ### Usage Usage information is included near the end of the stream (for example on `message_delta`), and includes `input_tokens` and `output_tokens`. When prompt caching is active, cache token fields may also appear. ## Responses Streaming (`/v1/responses`) Responses streams a sequence of typed objects. NanoGPT emits these as SSE `data:` frames containing JSON with a `type` field (for example `response.created`, `response.output_text.delta`, etc.). Example: ```text theme={null} data: {"type":"response.created","response":{...},"sequence_number":0} data: {"type":"response.output_text.delta","item_id":"msg_...","output_index":0,"content_index":0,"delta":"Hello","sequence_number":3} data: {"type":"response.completed","response":{...},"sequence_number":11} data: [DONE] ``` Terminal events include: * `response.completed` * `response.incomplete` * `response.failed` ### Tool Calls Tool calls appear as `function_call` output items. Arguments stream via `response.function_call_arguments.delta` frames and finish with `response.function_call_arguments.done`. ### Usage Usage appears on the terminal event inside the full `response` object (for example `response.completed.response.usage`). ## Error Handling Notes * If an error happens before streaming begins, you will receive a normal JSON error response with an HTTP status code. * If an error happens mid-stream, the stream may end early. Your parser should handle EOF without a terminal marker as an error/retry condition. For general status-code handling and retry guidance, see [Error Handling](/api-reference/miscellaneous/error-handling). ## Raw SSE Parsing Tips When parsing SSE manually: * Events are separated by a blank line (`\\n\\n` or `\\r\\n\\r\\n`). * A single event can include multiple `data:` lines; concatenate them with `\\n` before parsing as JSON. * Handle `[DONE]` as a sentinel string. # Tool Calling Diagnostics Source: https://docs.nano-gpt.com/api-reference/miscellaneous/tool-calling-diagnostics Opt in to share a failed tool-calling turn with NanoGPT support # Tool Calling Diagnostics If an agent or coding client fails while using tools through NanoGPT, you can opt in to share the failing turn with NanoGPT support for debugging. This is designed for cases where a model emits malformed tool calls, stops instead of calling a required tool, loops on a tool, or a client shows raw tool markup instead of executing the tool. ## Enable Diagnostics Tool-calling diagnostics are off by default. To enable them: 1. Open [Settings](https://nano-gpt.com/settings#privacy--analytics). 2. Enable tool-calling diagnostics under Privacy & Analytics. 3. Review what may be stored, choose a retention period, and acknowledge the capture terms. ## Capture A Failed Turn After a tool-calling failure, send this as the next message: ```text theme={null} nanogpt fix this ``` Use that exact lowercase phrase by itself. Do not include any other words. If diagnostics are not enabled yet, NanoGPT will respond with a link to the settings page instead of storing a capture. ## What Gets Stored When enabled, NanoGPT stores only the immediately previous request/response cycle visible in the trigger request history. The diagnostic capture can include: * prompts and conversation messages from that cycle * tool schemas, tool arguments, and tool outputs * file paths or other content your client included * model responses and client-visible error details * NanoGPT request metadata such as model, endpoint, request IDs, and stream mode NanoGPT applies best-effort redaction before storing the capture, but you should assume the captured turn may contain sensitive project or conversation data. Only use this feature when you are comfortable sharing that failing turn with NanoGPT support. ## Retention Diagnostic captures are temporary. The default retention period is 14 days, and captures are stored privately for support investigation. ## Limits Diagnostics are based on the conversation history your client sends with the `nanogpt fix this` trigger. If a client references earlier state by ID instead of including it in the request, NanoGPT may not be able to reconstruct the full failing turn from the trigger alone. # TypeScript Library Source: https://docs.nano-gpt.com/api-reference/miscellaneous/typescript TypeScript client for NanoGPT API # NanoGPT-client [NanoGPT-client](https://github.com/aspic/nanogpt-client) is an unofficial TypeScript implementation of the NanoGPT API. This library aims to provide a type-safe client for both browser and Node.js environments. ## Overview NanoGPT-client is built on the inferred OpenAPI spec, providing a strongly-typed interface to interact with the NanoGPT API. This makes it easier to integrate NanoGPT's capabilities into your TypeScript applications with full type checking and IntelliSense support. ## Installation Install the package via npm: ```bash theme={null} npm install nanogpt-client ``` Or using yarn: ```bash theme={null} yarn add nanogpt-client ``` ## Basic Usage ```typescript theme={null} import { NanoGPTClient } from 'nanogpt-client'; // Initialize with your API key const client = new NanoGPTClient({ apiKey: 'your-api-key' }); async function main() { try { const response = await client.chatCompletions.create({ model: 'openai/gpt-5.6-sol', messages: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Explain TypeScript interfaces.' } ] }); console.log(response.choices[0].message.content); } catch (error) { console.error('Error:', error); } } main(); ``` ## Features * **Type Safety**: Full TypeScript type definitions for all API endpoints and parameters * **Cross-Platform**: Works in both browser and Node.js environments * **Modern Architecture**: Built with modern TypeScript practices * **Comprehensive Coverage**: Supports all NanoGPT API endpoints ## API Methods ### Chat Completions ```typescript theme={null} const completion = await client.chatCompletions.create({ model: 'openai/gpt-5.6-sol', messages: [ { role: 'user', content: 'Hello, world!' } ], temperature: 0.7, max_tokens: 150 }); ``` ### Text Completions ```typescript theme={null} const completion = await client.completions.create({ model: 'openai/gpt-5.6-sol', prompt: 'Write a poem about TypeScript', max_tokens: 100 }); ``` ### Image Generation ```typescript theme={null} const image = await client.images.generate({ prompt: 'A cat programming in TypeScript', model: 'recraft-v3', n: 1, size: '1024x1024' }); ``` ### Video Generation ```typescript theme={null} const video = await client.videos.create({ prompt: 'A short animation of code being written', framework: 'emotional_story', targetLengthInWords: 70 }); ``` ### Check Balance ```typescript theme={null} const balance = await client.balance.check(); console.log('Current balance:', balance); ``` ## Advanced Configuration ```typescript theme={null} const client = new NanoGPTClient({ apiKey: 'your-api-key', baseUrl: 'https://custom-domain.com/api', // Optional custom API URL timeout: 30000, // Request timeout in ms headers: { 'Custom-Header': 'value' } }); ``` ## Development The library is open-source and welcomes contributions. To contribute: 1. Fork the [repository](https://github.com/aspic/nanogpt-client) 2. Clone your fork 3. Install dependencies (`npm install` or `yarn`) 4. Make your changes 5. Submit a pull request ## Resources * [GitHub Repository](https://github.com/aspic/nanogpt-client) * [NanoGPT API Documentation](https://docs.nano-gpt.com) * [Get your API Key](https://nano-gpt.com/api) # Video Input Source: https://docs.nano-gpt.com/api-reference/miscellaneous/video-input Send videos to compatible text and multimodal models through Chat Completions, Responses, or Messages. Video input lets compatible text and multimodal models understand a video. It is separate from [video generation](/api-reference/endpoint/video-generation): this guide covers video sent as input, not video creation, editing, or video-to-video generation. ## Choose a compatible model Request the detailed model catalog and select a model that advertises video input: ```http theme={null} GET /api/v1/models?detailed=true ``` Look for additive capability fields such as: ```json theme={null} { "architecture": { "input_modalities": ["text", "image", "video"] }, "capabilities": { "video_input": true } } ``` An explicitly selected model without video capability is rejected before provider dispatch and billing. Automatic fallback keeps only routes that preserve video input. ## Chat Completions Use the canonical OpenAI-compatible `video_url` content part with `POST /api/v1/chat/completions`: ```json theme={null} { "model": "google/gemini-3.1-flash-lite", "messages": [{ "role": "user", "content": [ { "type": "text", "text": "Describe this clip." }, { "type": "video_url", "video_url": { "url": "https://cdn.example.com/clip.mp4", "detail": "auto" } } ] }] } ``` `video_url.url` may be a public HTTPS URL or a `data:video/*;base64,...` URL. `detail` accepts `auto`, `low`, or `high` where the selected route supports it. The endpoint accepts compatibility aliases (`input_video`, direct `video`, and safely identifiable `input_file`/`file` blocks), but new integrations should emit `video_url`. A file block is classified as video only when NanoGPT can identify it from a `video/*` MIME type, a video data URL, or a recognized video filename/URL extension. Do not use an opaque file reference for video. See [Chat Completions](/api-reference/endpoint/chat-completion#video-input) for endpoint-specific examples. ## Responses Use `input_video` inside a message content array with `POST /api/v1/responses`: ```json theme={null} { "model": "google/gemini-3.1-flash-lite", "input": [{ "type": "message", "role": "user", "content": [ { "type": "input_text", "text": "Summarize the action." }, { "type": "input_video", "video_url": "data:video/mp4;base64,AAAA..." } ] }] } ``` Chat-style `video_url` is accepted as a compatibility alias. `input_file` is accepted as video only when its MIME type, data URL, or recognized filename/URL extension safely identifies video. PDFs, audio files, and unknown or opaque files are not silently treated as video. An opaque `file_id` is not resolved for Responses video input and returns `video_file_id_not_supported`. Responses Batch is separate and currently rejects video inputs. This change does not add video support to Batch. See [Responses](/api-reference/endpoint/responses#video-input) for the full endpoint contract. ## Anthropic Messages Use an Anthropic `video` content block with `POST /api/v1/messages`. For inline bytes, use the base64 source form: ```json theme={null} { "model": "google/gemini-3.1-flash-lite", "max_tokens": 512, "messages": [{ "role": "user", "content": [ { "type": "text", "text": "What happens in this clip?" }, { "type": "video", "source": { "type": "base64", "media_type": "video/mp4", "data": "AAAA..." } } ] }] } ``` For URL transport, use: ```json theme={null} { "type": "video", "source": { "type": "url", "url": "https://cdn.example.com/clip.mp4", "media_type": "video/mp4" } } ``` A `document` compatibility block with a `video/*` source is normalized as video. A document with `application/pdf` remains a document. Use `type: "video"` for new integrations. See [Messages](/api-reference/endpoint/messages#video-block) for endpoint-specific examples. ## Sources, limits, and security * Public remote sources must use HTTPS. Plain HTTP and other schemes are rejected. * Inline sources must be valid `data:video/*;base64,...` URLs, or raw base64 in an established direct field such as Anthropic `source.data` or a video-typed `input_file.file_data`. * Routes that fetch or materialize video apply SSRF protections, validate the fetched content type, allow at most 2 video attachments, and limit each attachment to 20 MB by default. Provider pass-through routes may impose stricter codec, duration, or size limits. * NanoGPT does not publish one universal duration or token conversion limit. Video remains a distinct modality for routing and billing; when duration metadata is unavailable, a conservative estimate may be reconciled against provider-reported usage. ## Segment selection `start_offset` and `end_offset` are measured in seconds and validated as finite, non-negative numbers (`end_offset` must be greater than `start_offset`). No current public text-model route can reliably honor these fields, so valid offsets return `video_segment_not_supported`; invalid ranges return `invalid_video_segment`. Do not present segment selection as supported. ## YouTube URLs YouTube pass-through is model- and route-dependent. An applicable direct Gemini route may accept a public YouTube URL. A route that would need NanoGPT to fetch or materialize it returns `youtube_video_route_not_supported`. NanoGPT does not download or scrape YouTube for video input. The separate opt-in YouTube transcript feature is not video understanding. ## Validation errors Video errors use the endpoint's normal OpenAI- or Anthropic-compatible error envelope. Common `400` codes include: | Code | Meaning | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `video_input_not_supported` | The selected model does not support video input. | | `invalid_video_url_scheme` | A non-HTTPS remote source was used. Chat Completions may expose `video_attachment_url_invalid` as an established alias. | | `invalid_video_data_url` | The data URL is malformed or is not a video data URL. | | `invalid_video_base64` | Inline base64 is malformed. | | `invalid_video_mime_type` | An explicit non-video MIME type was used for video content. | | `missing_video_source` | The content block has no usable URL or data. | | `video_file_id_not_supported` | An unresolved uploaded-file reference was used. | | `video_attachment_too_large` | A materialized attachment exceeded the route limit. | | `video_attachment_limit_exceeded` | Too many materialized video attachments were supplied. | | `invalid_video_segment` | Segment offsets are invalid. | | `video_segment_not_supported` | The selected route cannot honor segment selection. | | `youtube_video_route_not_supported` | The selected route cannot accept the YouTube URL directly. | # Accountless x402 API Payments Source: https://docs.nano-gpt.com/api-reference/miscellaneous/x402 Opt in to accountless payment quotes for supported NanoGPT API endpoints, pay with a supported crypto rail, and complete or replay the original request. ## Overview NanoGPT supports accountless API calls for selected endpoints. To request an accountless x402 quote, send the API request without `Authorization` or `x-api-key`, and include `x-x402: true`. NanoGPT will return `402 Payment Required` with available payment options. After paying, call the quoted `completeUrl`, replay the original endpoint with `X-PAYMENT`, or replay the original endpoint with `Authorization: L402 :` for Lightning L402. An unauthenticated API request without `x-x402: true` is treated as a normal unauthenticated request and returns `401 missing_api_key`. NanoGPT does not generate x402 quotes for every unauthenticated request. Live demo: [https://nano-gpt.com/x402](https://nano-gpt.com/x402) Only endpoints returned by `GET /api/v1/x402/endpoints` should be treated as public accountless x402 endpoints. Treat broad x402 support claims as release-gated by live smoke tests against localhost and production. Streaming chat/responses and long-running video generation have implementation coverage but are not the stable public contract yet. ## Endpoint Matrix The supported endpoint matrix is machine-readable. Its `schemes` list reflects the payment rails enabled in the current deployment. ```bash theme={null} curl https://nano-gpt.com/api/v1/x402/endpoints ``` As of 2026-06-14, production advertises `lightning-l402` on the public endpoints listed below. Continue to use `GET /api/v1/x402/endpoints` as the source of truth because enabled rails can be deployment-gated. Use `?supported=all` to include beta and deferred routes: ```bash theme={null} curl "https://nano-gpt.com/api/v1/x402/endpoints?supported=all" ``` If x402 is disabled, payment storage is unavailable, or completion replay is not configured, the default supported list is empty. The matrix is generated from `lib/x402/publicEndpointContract.ts`, which is also used by the runnable smoke test examples. Current public accountless endpoints: | Method | Endpoint | Stable accountless scope | Quote type | Notes | | ------ | ---------------------------- | ------------------------ | ----------------------------- | ------------------------------------------------------------------ | | `POST` | `/api/v1/images/generations` | JSON requests | deterministic | Image generation. | | `POST` | `/api/v1/images/edits` | JSON requests | deterministic | Multipart uploads require normal auth before conversion. | | `POST` | `/api/v1/chat/completions` | non-streaming requests | estimated with reconciliation | Set `stream: false` or omit streaming. | | `POST` | `/api/v1/responses` | non-streaming requests | estimated with reconciliation | Streaming and background mode are not stable accountless contract. | | `POST` | `/api/v1/data/web/search` | JSON requests | deterministic | Public v1 data path for web search. | | `POST` | `/api/v1/data/url/scrape` | JSON requests | deterministic | Public v1 data path for URL scraping. Use `urls: [...]`. | Only endpoints that return a valid 402 quote in smoke tests should be listed as supported. Do not assume other API endpoints support accountless payment until they appear as supported in the matrix and have passed live smoke tests. ## Payment Schemes Production 402 quotes currently advertise these schemes when enabled: | Public scheme | Network | Asset | Flow | | ------------------ | ----------------------------- | ----- | ---------------------------------------------------------------------------------------------------------- | | `nano` | `nano-mainnet` | XNO | Send XNO to a unique Nano address, then call `completeUrl`. | | `nano-exact` | Nano mainnet exact settlement | XNO | Sign an exact payment payload and replay the original request with `X-PAYMENT`. | | `base-usdc` | Base | USDC | Send USDC on Base to a unique address, then call `completeUrl`. | | `x402-exact` | Base | USDC | Use an EIP-3009 authorization in `X-PAYMENT`, then replay the original request. | | `x402-solana-usdc` | Solana/SVM | USDC | Use a Solana/SVM exact payment in `X-PAYMENT`, then replay the original request. | | `lightning-l402` | `bitcoin-lightning` | sats | Pay the Lightning invoice, then replay the original request with `Authorization: L402 :`. | Solana USDT and native SOL are not documented x402 exact rails. Do not list either as a supported accountless x402 scheme until it appears in `GET /api/v1/x402/endpoints`. ## Working Quote Examples All initial quote examples below intentionally omit `Authorization` and `x-api-key` and include `x-x402: true`. ### Text Chat ```bash theme={null} curl -i https://nano-gpt.com/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "model": "gpt-4.1-nano", "messages": [{"role": "user", "content": "Say ok"}], "stream": false }' ``` ### Image Generation ```bash theme={null} curl -i https://nano-gpt.com/api/v1/images/generations \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "model": "gpt-image-1", "prompt": "a product photo of a brushed steel desk lamp", "size": "1024x1024", "n": 1 }' ``` ### Web Search ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/web/search \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "query": "NanoGPT x402 accountless payments", "max_results": 3 }' ``` ### URL Scrape ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/url/scrape \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "urls": ["https://nano-gpt.com"] }' ``` ## Flow 1: Quote, Pay, Complete To request an accountless x402 quote, send the API request without `Authorization` or `x-api-key`, and include `x-x402: true`. NanoGPT will return `402 Payment Required` with available payment options. ```bash theme={null} curl -i https://nano-gpt.com/api/v1/images/generations \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "model": "gpt-image-1", "prompt": "a product photo of a brushed steel desk lamp", "size": "1024x1024", "n": 1 }' ``` For accountless payment challenges, key off the stable top-level `payment` object. The `error` object is kept for OpenAI-compatible error handling and may vary by endpoint. The legacy `accepts`, `x402Version`, and top-level `requestHash` fields may still appear for backwards compatibility and advanced protocol clients. ```json theme={null} { "error": { "type": "insufficient_quota", "code": "insufficient_quota", "message": "Payment required to complete this request." }, "payment": { "version": 1, "paymentId": "pay_...", "requestHash": "sha256:...", "expiresAt": "2026-06-09T12:00:00.000Z", "amountUsd": "0.0714", "statusUrl": "https://nano-gpt.com/api/x402/status/pay_...", "completeUrl": "https://nano-gpt.com/api/x402/complete/pay_...", "accepted": [ { "scheme": "nano", "protocolScheme": "nano", "network": "nano-mainnet", "amount": "...", "amountFormatted": "0.17067988 XNO", "amountUsd": "0.0714", "payTo": "nano_...", "paymentId": "pay_...", "statusUrl": "https://nano-gpt.com/api/x402/status/pay_...", "completeUrl": "https://nano-gpt.com/api/x402/complete/pay_..." }, { "scheme": "x402-solana-usdc", "protocolScheme": "exact", "network": "solana", "amount": "1500", "amountFormatted": "0.0015 USDC", "amountUsd": "0.0015", "payTo": "", "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "feePayer": "", "paymentId": "pay_...", "expiresAt": "2026-06-09T12:00:00.000Z" }, { "scheme": "lightning-l402", "protocolScheme": "lightning-l402", "network": "bitcoin-lightning", "amount": "9", "amountFormatted": "9 sats", "amountUsd": "0.0054", "payTo": "lnbc...", "invoice": "lnbc...", "paymentHash": "...", "l402Token": "...", "discountRate": 0.1, "undiscountedAmountUsd": "0.006" } ] }, "x402Version": 1, "accepts": [] } ``` Treat the presence of `payment` as the accountless payment signal; do not depend on a single legacy `error.code` value. Top-level `payment.statusUrl` and `payment.completeUrl` mirror the primary advertised payment option when that option is a polling-style rail. When choosing a specific payment scheme, prefer the fields inside that option in `payment.accepted[]`. Exact replay options such as `nano-exact`, `x402-exact`, and `x402-solana-usdc` do not include `statusUrl` or `completeUrl`; successful replay settles through the `X-PAYMENT` response path rather than updating the original quote record. Lightning L402 options also do not use `completeUrl`; successful completion is the replay of the original endpoint with the `Authorization: L402 :` header. After paying a `nano` or `base-usdc` quote, call: ```bash theme={null} curl -i -X POST "https://nano-gpt.com/api/x402/complete/pay_..." ``` Completion replays the stored original request. A payment is bound to the original method, path, body hash, quoted amount, and expiration. Expired, underpaid, already processing, or already completed payments are rejected. Nano quote-and-complete rails use `scheme: "nano"` and a unique `payTo` Nano address. Base USDC quote-and-complete rails use `scheme: "base-usdc"` and a unique Base address. For either rail, clients should copy the option-specific `amountFormatted`, `payTo`, `statusUrl`, and `completeUrl` from `payment.accepted[]`. ## Flow 2: x402 Exact Replay For `nano-exact`, `x402-exact`, and `x402-solana-usdc`, sign the advertised exact payment requirement and replay the original endpoint with: ```bash theme={null} curl -i https://nano-gpt.com/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "X-PAYMENT: $BASE64_PAYMENT_PAYLOAD" \ -d '{ "model": "gpt-4.1-nano", "messages": [{"role": "user", "content": "Say ok"}] }' ``` Successful exact-settlement responses include `X-PAYMENT-RESPONSE` when a settlement hash is available. For Solana USDC, select the `x402-solana-usdc` option from `payment.accepted[]` and use an x402 Solana/SVM-compatible client, such as `@x402/svm`, to create and sign the exact payment from the quote requirements. `amount` is in USDC atomic units with 6 decimals. `asset` is the Solana USDC mint, `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`. Copy `payTo` and `feePayer` from the quote; do not hardcode either value. Lower-level x402 clients can also read the top-level `accepts[]` protocol object. For Solana USDC exact payments, it uses the official exact shape: ```json theme={null} { "scheme": "exact", "network": "solana", "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "maxAmountRequired": "1500", "payTo": "", "extra": { "feePayer": "", "tokenSymbol": "USDC", "tokenDecimals": 6 } } ``` The exact replay request must use the same method, path, and JSON body that produced the quote: ```bash theme={null} curl -i https://nano-gpt.com/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "X-PAYMENT: $BASE64_PAYMENT_PAYLOAD" \ -d '{ "model": "gpt-4.1-nano", "messages": [{"role": "user", "content": "Say ok"}], "stream": false }' ``` NanoGPT validates exact `X-PAYMENT` replay against the quoted resource, amount, and expiration. Clients should replay the same request they quoted unless endpoint docs state otherwise. Example Solana USDC replay: ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/url/scrape \ -H "Content-Type: application/json" \ -H "X-PAYMENT: $BASE64_SOLANA_X402_PAYMENT" \ -d '{ "urls": ["https://nano-gpt.com"] }' ``` Successful Solana exact-settlement responses include a base64 JSON `X-PAYMENT-RESPONSE` header. After decoding, the shape is: ```json theme={null} { "success": true, "hash": "", "network": "solana" } ``` The full live Solana USDC x402 flow was tested successfully on 2026-06-09 against `POST https://nano-gpt.com/api/v1/data/url/scrape`. The request paid 1500 atomic USDC, or \$0.0015, via `x402-solana-usdc` and returned 200 with an `X-PAYMENT-RESPONSE` header for `network: "solana"`. On-chain Solana signature: ```text theme={null} 5yqd8zJCNMqJBShKboJkDr4fDyFiWe8cawC6gGCuYD2zwqrbSUhQYws3w5YdEMeCL2cpyhuWG5en7zMYabjZyJRx ``` ## Flow 3: Lightning L402 Replay NanoGPT also supports Lightning L402 for accountless API payments. Instead of signing an `X-PAYMENT` payload, the client pays a Lightning invoice and proves payment by replaying the original request with the invoice preimage. To request an accountless Lightning L402 quote, send the original request without `Authorization` or `x-api-key`, and include `x-x402: true`: ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/web/search \ -H "Content-Type: application/json" \ -H "x-x402: true" \ -d '{ "query": "latest AI agent payment protocols", "max_results": 3 }' ``` When `lightning-l402` is enabled, the `402 Payment Required` response includes a Lightning option in `payment.accepted[]` and a `WWW-Authenticate: Payment ...` challenge header. For most API clients, the easiest path is to read `payment.accepted[].invoice` or `payment.accepted[].payTo`, then read `payment.accepted[].l402Token` for the replay token. The challenge header is useful for clients that already implement L402 or HTTP auth challenge handling. Example Lightning option: ```json theme={null} { "scheme": "lightning-l402", "protocolScheme": "lightning-l402", "network": "bitcoin-lightning", "amount": "9", "amountFormatted": "9 sats", "amountUsd": "0.0054", "payTo": "lnbc...", "invoice": "lnbc...", "paymentHash": "...", "l402Token": "...", "discountRate": 0.1, "undiscountedAmountUsd": "0.006" } ``` The `WWW-Authenticate` challenge includes the same token in `id=""` and a base64-encoded request value: ```http theme={null} WWW-Authenticate: Payment id="", realm="https://nano-gpt.com", method="lightning", intent="charge", request="", description="...", expires="..." ``` After decoding the `request` value, the JSON contains the Lightning invoice: ```json theme={null} { "amount": "9", "currency": "sat", "methodDetails": { "invoice": "lnbc...", "network": "mainnet", "paymentHash": "..." } } ``` Pay the advertised invoice with a Lightning wallet, node, or payment API that returns the payment preimage. Some consumer Lightning wallets do not expose the payment preimage; L402 clients need a wallet, node, or payment API that returns it after payment. Replay the exact original request with the same method, path, and JSON body, adding: ```http theme={null} Authorization: L402 : ``` ```bash theme={null} curl -i https://nano-gpt.com/api/v1/data/web/search \ -H "Content-Type: application/json" \ -H "Authorization: L402 $L402_TOKEN:$PAYMENT_PREIMAGE" \ -d '{ "query": "latest AI agent payment protocols", "max_results": 3 }' ``` For chat completions, keep accountless L402 requests non-streaming: ```bash theme={null} curl -i https://nano-gpt.com/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: L402 $L402_TOKEN:$PAYMENT_PREIMAGE" \ -d '{ "model": "gpt-4.1-nano", "messages": [{"role": "user", "content": "Say ok"}], "stream": false }' ``` The L402 token is bound to the original method, path, request body hash, amount, and expiration. The token/preimage pair is single-use and expires with the quote. Lightning L402 quotes currently receive a 10% discount versus the normal accountless quote; when present, the Lightning option exposes `discountRate` and `undiscountedAmountUsd`. Minimal client logic: ```ts theme={null} const original = { path: '/api/v1/data/web/search', body: { query: 'latest AI agent payment protocols', max_results: 3, }, }; const quoteResponse = await fetch(`https://nano-gpt.com${original.path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-x402': 'true', }, body: JSON.stringify(original.body), }); if (quoteResponse.status !== 402) { throw new Error(`Expected 402, got ${quoteResponse.status}`); } const quote = await quoteResponse.json(); const lightning = quote.payment.accepted.find( (option: any) => option.scheme === 'lightning-l402' ); if (!lightning) { throw new Error('Lightning L402 is not advertised for this endpoint'); } // Pay lightning.invoice with a Lightning client that returns the preimage. const preimage = await payLightningInvoiceAndReturnPreimage(lightning.invoice); const paidResponse = await fetch(`https://nano-gpt.com${original.path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `L402 ${lightning.l402Token}:${preimage}`, }, body: JSON.stringify(original.body), }); const result = await paidResponse.json(); console.log(result); ``` ## Status Polling For quote-and-complete rails such as `nano` and `base-usdc`, use the quoted `statusUrl` to watch payment state: ```bash theme={null} curl "https://nano-gpt.com/api/x402/status/pay_..." ``` Known statuses: * `pending` * `underpaid` * `paid` * `processing` * `completed` * `failed` * `refunded` * `expired` Status responses include `pollAfterSeconds`, plus `Retry-After` and `X-Poll-After` headers. Async media status URLs are not documented as stable accountless x402 behavior until signed unauthenticated status and media URL behavior is smoke-tested and expiration is explicitly documented. ## Troubleshooting If you receive `401 missing_api_key` immediately, check that the initial quote request includes `x-x402: true`. Without that header, NanoGPT does not enter the x402 quote flow. ## Reconciliation Image and data endpoint quotes are deterministic for the submitted request parameters. Chat Completions and Responses quotes are estimates and are reconciled after the provider returns usage. If provider execution fails after direct x402 settlement, the endpoint-specific failure path attempts the documented refund or failed-fee reconciliation. For `completeUrl`, NanoGPT replays the stored original request, so the completed request is bound to the quoted method, path, and body. For exact `X-PAYMENT` replay, NanoGPT validates the payment against the quoted resource, amount, and expiration. For Lightning L402 replay, NanoGPT validates the token and preimage against the quoted method, path, request body hash, amount, and expiration. Clients should replay the same request they quoted unless endpoint docs state otherwise. Tool follow-up replay is separately constrained to trusted tool-call continuations. Do not assume every accountless x402 payment is an exact final charge. ## Helper Examples And Smoke Tests Helper examples live in `examples/x402`. They cover: * quote request * choosing a payment rail * status polling * `completeUrl` * exact replay with `X-PAYMENT` * Lightning L402 replay with `Authorization: L402 :` * expired, underpaid, and failed completion handling via status/complete responses Run the smoke tests against localhost or production: ```bash theme={null} pnpm x402:smoke -- --base http://localhost:3000 pnpm x402:smoke -- --base https://nano-gpt.com ``` Paid smoke paths are opt-in because the payment payload is wallet-specific: ```bash theme={null} X402_PAYMENT_HEADER="$BASE64_PAYMENT_PAYLOAD" pnpm x402:smoke -- --endpoint chat-completions X402_COMPLETE_URL="https://nano-gpt.com/api/x402/complete/pay_..." pnpm x402:smoke ``` ## Safety Notes * `completeUrl` payments are bound to the original request method, path, body hash, quote amount, and expiration. * Exact `X-PAYMENT` replay is validated against the quoted resource, amount, and expiration. * Lightning L402 replay is validated against the quoted method, path, request body hash, amount, and expiration. * `Authorization: L402 :` is bearer payment proof. Do not log full L402 authorization headers. * Expired payments cannot be completed. * Underpaid payments cannot be completed. * Completed or processing payments cannot be completed twice. * Store the original request only as long as needed for payment completion. * Do not log `X-PAYMENT` payloads, private keys, provider credentials, or full request bodies beyond existing request logging policy. # Music Generation Source: https://docs.nano-gpt.com/api-reference/music-generation Generate music from text prompts using NanoGPT's OpenAI-compatible audio/speech endpoint. ## Overview NanoGPT supports AI music generation through the same OpenAI-compatible Text-to-Speech endpoint. When you specify a music model, the `input` field is treated as a music prompt (not text to speak) and the API returns an audio file. ## Endpoint ```http theme={null} POST https://nano-gpt.com/api/v1/audio/speech ``` ## Choosing A Music Model Music model availability changes over time. Discover available audio models via `GET https://nano-gpt.com/api/v1/audio-models` (see [Audio Models](/api-reference/endpoint/audio-models)) and select a model intended for music generation. ## Request Format ```json theme={null} { "model": "YOUR_MUSIC_MODEL_ID", "input": "A chill lo-fi hip hop beat with soft piano chords and vinyl crackle, 120 BPM" } ``` If you are using an OpenAI client that enforces the OpenAI TTS schema (for example the official OpenAI SDK), you may need to include a `voice` field. For music models, `voice` is ignored, so it can be any string (many examples use `"alloy"`). ### Parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- | | `model` | string | Yes | A music-capable audio model ID (discover via `GET https://nano-gpt.com/api/v1/audio-models`) | | `input` | string | Yes | A text prompt describing the music you want generated | | `voice` | string | No | Ignored for music models. Some OpenAI-compatible clients require this field; if so, pass any string (for example `"alloy"`). | ## Response The response is an audio file (typically MP3). The `Content-Type` header indicates the format. ```http theme={null} HTTP/1.1 200 OK Content-Type: audio/mpeg ``` The response body is raw audio bytes. ## Examples ```bash cURL theme={null} curl -X POST https://nano-gpt.com/api/v1/audio/speech \ -H "Authorization: Bearer $NANOGPT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "YOUR_MUSIC_MODEL_ID", "input": "An energetic electronic dance track with heavy bass drops and synth leads" }' \ --output music.mp3 ``` ```python Python (OpenAI SDK) theme={null} from openai import OpenAI client = OpenAI( base_url="https://nano-gpt.com/api/v1", api_key="YOUR_API_KEY", ) response = client.audio.speech.create( model="YOUR_MUSIC_MODEL_ID", input="A peaceful acoustic guitar melody with soft drums", voice="alloy", # Ignored for music models; included for OpenAI SDK compatibility ) response.stream_to_file("output.mp3") ``` ## Tips * Be descriptive: include genre, instruments, tempo (BPM), mood, and style. * Duration: generation duration varies by model. Most models produce \~10-30 seconds by default; duration may be influenced by the prompt. * Cost/quality vary by model. If cost predictability matters, prefer models with flat per-generation pricing (when available). # Speech-to-Text (STT) Source: https://docs.nano-gpt.com/api-reference/speech-to-text Complete guide to speech-to-text transcription APIs ## Overview The NanoGPT STT API allows you to transcribe audio files into text using state-of-the-art speech recognition models. The API supports multiple languages, speaker diarization, and various audio formats with both synchronous and asynchronous processing options. For drop-in OpenAI SDK compatibility, you can also use the OpenAI-compatible endpoint: `POST /api/v1/audio/transcriptions` (see `api-reference/endpoint/audio-transcriptions.mdx`). ## Available Models NanoGPT supports multiple Speech-to-Text and audio-to-text workflows, including standard transcription, video transcription, and voice cloning. | Model ID | Type | Billing | Price | | ----------------------------------- | ----------------------------------- | ---------- | -------------- | | `Whisper-Large-V3` | Transcription | Per minute | \~\$0.0005/min | | `Wizper` | Transcription | Per minute | \$0.01/min | | `Elevenlabs-STT` | Transcription (async + diarization) | Per minute | \$0.03/min | | `gpt-4o-mini-transcribe` | Transcription | Per minute | \$0.003/min | | `gpt-4o-mini-transcribe-2025-03-20` | Transcription | Per minute | \$0.003/min | | `gpt-4o-mini-transcribe-2025-12-15` | Transcription | Per minute | \$0.003/min | | `gpt-4o-mini-transcribe-latest` | Transcription | Per minute | \$0.003/min | | `openai-whisper-with-video` | Video transcription | Per minute | \$0.06/min | | `qwen-voice-clone` | Voice cloning (async) | Per run | \$0.25/run | | `minimax-voice-clone` | Voice cloning (async) | Per run | \$1.00/run | ## Authentication All requests require authentication via API key: ```http theme={null} x-api-key: YOUR_API_KEY ``` ## File Upload Methods ### Method 1: Direct File Upload (≤3MB) For smaller audio files, upload directly using multipart/form-data: ```python theme={null} import requests BASE_URL = "https://nano-gpt.com/api" API_KEY = "YOUR_API_KEY" def transcribe_file(file_path, model="Whisper-Large-V3", language="auto"): """ Transcribe an audio file using direct upload """ headers = {"x-api-key": API_KEY} with open(file_path, 'rb') as audio_file: files = { 'audio': ('audio.mp3', audio_file, 'audio/mpeg') } data = { 'model': model, 'language': language } response = requests.post( f"{BASE_URL}/transcribe", headers=headers, files=files, data=data ) if response.status_code == 200: return response.json() else: raise Exception(f"Error: {response.status_code} - {response.text}") # Example usage try: result = transcribe_file("meeting.mp3", model="Whisper-Large-V3", language="en") print("Transcription:", result['transcription']) print("Cost:", result['metadata']['cost']) print("Duration:", result['metadata']['chargedDuration'], "minutes") except Exception as e: print(f"Error: {e}") ``` ### Method 2: URL Upload (Recommended for >3MB) For larger files, use URL-based upload: ```python theme={null} def transcribe_url(audio_url, model="Whisper-Large-V3", language="auto"): """ Transcribe an audio file from URL """ headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } data = { "audioUrl": audio_url, "model": model, "language": language } response = requests.post( f"{BASE_URL}/transcribe", headers=headers, json=data ) if response.status_code == 200: return response.json() else: raise Exception(f"Error: {response.status_code} - {response.text}") # Example usage audio_url = "https://example.com/large-audio-file.mp3" result = transcribe_url(audio_url, model="Wizper") print("Transcription:", result['transcription']) ``` ## Advanced Features with Elevenlabs-STT ### Speaker Diarization Identify and label different speakers in conversations: ```python theme={null} import time def transcribe_with_diarization(audio_url): """ Transcribe with speaker identification (async) """ headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } # Submit transcription job data = { "audioUrl": audio_url, "model": "Elevenlabs-STT", "diarize": True, "tagAudioEvents": True, "language": "auto" } response = requests.post( f"{BASE_URL}/transcribe", headers=headers, json=data ) if response.status_code == 202: job_data = response.json() return poll_for_results(job_data) else: raise Exception(f"Error: {response.status_code}") def poll_for_results(job_data): """ Poll for transcription results """ headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } status_data = { "runId": job_data['runId'], "cost": job_data.get('cost'), "paymentSource": job_data.get('paymentSource'), "isApiRequest": True, "fileName": job_data.get('fileName'), "fileSize": job_data.get('fileSize'), "chargedDuration": job_data.get('chargedDuration'), "diarize": job_data.get('diarize', False) } max_attempts = 60 for attempt in range(max_attempts): print(f"Checking status... (attempt {attempt + 1}/{max_attempts})") response = requests.post( f"{BASE_URL}/transcribe/status", headers=headers, json=status_data ) if response.status_code == 200: result = response.json() status = result.get('status') if status == 'completed': return result elif status == 'failed': raise Exception(f"Transcription failed: {result.get('error')}") time.sleep(5) # Wait 5 seconds before next check raise Exception("Transcription timed out") # Example usage conversation_url = "https://example.com/meeting-recording.mp3" try: result = transcribe_with_diarization(conversation_url) print("Full Transcription:", result['transcription']) print("\nSpeaker Breakdown:") if 'diarization' in result: for segment in result['diarization']['segments']: print(f"{segment['speaker']} ({segment['start']}-{segment['end']}s): {segment['text']}") # Word-level timestamps if 'words' in result: print("\nWord-level timestamps:") for word in result['words'][:10]: # Show first 10 words if word['type'] == 'word': print(f"'{word['text']}' at {word['start']}-{word['end']}s") except Exception as e: print(f"Error: {e}") ``` ## Language Support The API supports 97+ languages with auto-detection: ```python theme={null} # Common language codes SUPPORTED_LANGUAGES = { "auto": "Auto-detect", "en": "English", "es": "Spanish", "fr": "French", "de": "German", "it": "Italian", "pt": "Portuguese", "zh": "Chinese", "ja": "Japanese", "ko": "Korean", "ar": "Arabic", "hi": "Hindi", "ru": "Russian" } def transcribe_multilingual(audio_files): """ Transcribe multiple files with different languages """ results = [] for file_info in audio_files: try: result = transcribe_url( file_info['url'], language=file_info.get('language', 'auto') ) results.append({ 'file': file_info['name'], 'language': result['metadata']['language'], 'transcription': result['transcription'], 'cost': result['metadata']['cost'] }) except Exception as e: results.append({ 'file': file_info['name'], 'error': str(e) }) return results # Example usage audio_files = [ {"name": "english.mp3", "url": "https://example.com/english.mp3", "language": "en"}, {"name": "spanish.mp3", "url": "https://example.com/spanish.mp3", "language": "es"}, {"name": "unknown.mp3", "url": "https://example.com/unknown.mp3", "language": "auto"} ] results = transcribe_multilingual(audio_files) for result in results: if 'error' not in result: print(f"{result['file']} ({result['language']}): {result['transcription'][:100]}...") else: print(f"{result['file']}: Error - {result['error']}") ``` ## Complete Class Implementation Here's a complete transcriber class with error handling and retry logic: ```python theme={null} import requests import time import json from pathlib import Path class NanoGPTTranscriber: def __init__(self, api_key): self.api_key = api_key self.base_url = "https://nano-gpt.com/api" def transcribe(self, audio_path=None, audio_url=None, **kwargs): """ Transcribe audio with automatic method selection """ if audio_path and audio_url: raise ValueError("Specify either audio_path or audio_url, not both") if audio_path: return self._transcribe_file(audio_path, **kwargs) elif audio_url: return self._transcribe_url(audio_url, **kwargs) else: raise ValueError("Either audio_path or audio_url must be provided") def _transcribe_file(self, audio_path, **kwargs): """Direct file upload transcription""" headers = {"x-api-key": self.api_key} path = Path(audio_path) if path.stat().st_size > 3 * 1024 * 1024: # 3MB raise ValueError("File too large for direct upload. Use audio_url method.") with open(audio_path, 'rb') as f: files = {'audio': (path.name, f.read(), 'audio/mpeg')} data = self._prepare_request_data(**kwargs) response = requests.post( f"{self.base_url}/transcribe", headers=headers, files=files, data=data ) return self._handle_response(response) def _transcribe_url(self, audio_url, **kwargs): """URL-based transcription""" headers = { "x-api-key": self.api_key, "Content-Type": "application/json" } data = {"audioUrl": audio_url} data.update(self._prepare_request_data(**kwargs)) response = requests.post( f"{self.base_url}/transcribe", headers=headers, json=data ) return self._handle_response(response) def _prepare_request_data(self, **kwargs): """Prepare request data with defaults""" data = { "model": kwargs.get("model", "Whisper-Large-V3"), "language": kwargs.get("language", "auto") } # Add optional parameters if kwargs.get("diarize"): data["diarize"] = "true" if isinstance(kwargs["diarize"], bool) else kwargs["diarize"] if kwargs.get("tagAudioEvents"): data["tagAudioEvents"] = "true" if isinstance(kwargs["tagAudioEvents"], bool) else kwargs["tagAudioEvents"] if kwargs.get("actualDuration"): data["actualDuration"] = str(kwargs["actualDuration"]) return data def _handle_response(self, response): """Handle API response""" if response.status_code == 200: return response.json() elif response.status_code == 202: return self._poll_async_job(response.json()) else: try: error_data = response.json() raise Exception(f"API Error: {error_data.get('error', 'Unknown error')}") except json.JSONDecodeError: raise Exception(f"HTTP Error: {response.status_code}") def _poll_async_job(self, job_data): """Poll for async job completion""" headers = { "x-api-key": self.api_key, "Content-Type": "application/json" } status_data = { "runId": job_data['runId'], "cost": job_data.get('cost'), "paymentSource": job_data.get('paymentSource'), "isApiRequest": True, "fileName": job_data.get('fileName'), "fileSize": job_data.get('fileSize'), "chargedDuration": job_data.get('chargedDuration'), "diarize": job_data.get('diarize', False) } max_attempts = 60 for attempt in range(max_attempts): time.sleep(5) response = requests.post( f"{self.base_url}/transcribe/status", headers=headers, json=status_data ) if response.status_code == 200: result = response.json() if result.get('status') == 'completed': return result elif result.get('status') == 'failed': raise Exception(f"Transcription failed: {result.get('error')}") raise Exception("Transcription timed out") def format_diarization(self, result): """Format transcription with speaker labels""" if 'diarization' in result and 'segments' in result['diarization']: segments = result['diarization']['segments'] return '\n\n'.join([ f"{seg['speaker']}: {seg['text']}" for seg in segments ]) return result.get('transcription', '') # Usage examples transcriber = NanoGPTTranscriber("YOUR_API_KEY") # Simple transcription result = transcriber.transcribe( audio_path="meeting.mp3", model="Whisper-Large-V3", language="en" ) print("Transcription:", result['transcription']) # Advanced with speaker diarization result = transcriber.transcribe( audio_url="https://example.com/conversation.mp3", model="Elevenlabs-STT", diarize=True, tagAudioEvents=True ) print("Formatted conversation:") print(transcriber.format_diarization(result)) ``` ## Error Handling and Best Practices ### Common Error Responses ```python theme={null} def handle_transcription_errors(func): """Decorator for handling common transcription errors""" def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except requests.exceptions.HTTPError as e: if e.response.status_code == 400: print("Bad Request: Check file format or parameters") elif e.response.status_code == 401: print("Unauthorized: Check your API key") elif e.response.status_code == 402: print("Insufficient balance: Top up your account") elif e.response.status_code == 413: print("File too large: Use URL upload for files >3MB") else: print(f"HTTP Error {e.response.status_code}") except Exception as e: print(f"Error: {str(e)}") return wrapper @handle_transcription_errors def safe_transcribe(transcriber, **kwargs): return transcriber.transcribe(**kwargs) ``` ### File Format Support ```python theme={null} SUPPORTED_FORMATS = { '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.m4a': 'audio/mp4', '.ogg': 'audio/ogg', '.aac': 'audio/aac' } def validate_audio_file(file_path): """Validate audio file format and size""" path = Path(file_path) if not path.exists(): raise FileNotFoundError(f"File not found: {file_path}") if path.suffix.lower() not in SUPPORTED_FORMATS: raise ValueError(f"Unsupported format: {path.suffix}") size_mb = path.stat().st_size / (1024 * 1024) if size_mb > 3: print(f"Warning: File is {size_mb:.1f}MB. Consider using URL upload.") return True ``` ## Pricing and Billing Transcription models are billed by audio/video duration. Voice cloning models are billed per run. ### Transcription (per minute) * **Whisper-Large-V3**: \~\$0.0005/min * **Wizper**: \$0.01/min * **Elevenlabs-STT**: \$0.03/min * **gpt-4o-mini-transcribe** (and snapshots/aliases): \$0.003/min * **openai-whisper-with-video**: \$0.06/min ### Voice cloning (per run) * **qwen-voice-clone**: \$0.25/run * **minimax-voice-clone**: \$1.00/run Costs are calculated based on audio/video duration (transcription) or per run (voice cloning). # Teams Source: https://docs.nano-gpt.com/api-reference/teams Complete API reference for the NanoGPT Teams feature ## Overview The Teams API enables programmatic management of teams, members, invitations, usage tracking, and access control. **Base URL**: `/api/teams` **Authentication**: All endpoints require session authentication unless otherwise noted. **Team Identifiers**: Endpoints accept either UUID (`550e8400-e29b-xxxx-xxxxx-xxxxx`) or numeric ID (`123`). *** ## Default Team Selection If you belong to multiple teams, you can set a default team. The default team affects which team context is used by the NanoGPT web app and other session-authenticated requests that support team billing and settings. ### Set Default Team ``` PATCH /api/user/default-team ``` **Request Body** (all fields optional): ```json theme={null} { "team_uuid": "550e8400-e29b-xxxx-xxxx-xxxxxxxxxxxx" } ``` Or by numeric ID: ```json theme={null} { "team_id": 123 } ``` To clear the default team and return to personal billing: ```json theme={null} { "team_uuid": null } ``` **Response**: ```json theme={null} { "ok": true, "metadata": { "default_team_uuid": "550e8400-e29b-xxxx-xxxx-xxxxxxxxxxxx" } } ``` *** ## User Response Retention Default Set a user-level default retention for `/v1/responses`. This applies when a request does not provide `retention_days` or `retentionDays` and no team override is active. This is currently exposed through the API endpoint below, not as a visible control in the main web Settings page. ### Get User Responses Retention ``` GET /api/user/responses-retention ``` **Response**: ```json theme={null} { "responsesRetentionDays": 0 } ``` ### Set User Responses Retention ``` POST /api/user/responses-retention ``` **Request Body**: ```json theme={null} { "responsesRetentionDays": 0 } ``` To clear the user-level override and fall back to team/platform defaults: ```json theme={null} { "responsesRetentionDays": null } ``` Rules: * `responsesRetentionDays` accepts integer values `0..365` or `null`. * `null` clears the user-level override. * The setting is stored in `sessions.metadata.responsesRetentionDays`. See [Responses: response storage and retention](/api-reference/endpoint/responses#response-storage-and-retention) for per-request `store`, `retentionDays`, and `retention_days` behavior. *** ## Referral Link Referral links let you share a signup link that is tied to your account. ### Get or Create Referral Link ``` GET /api/subscription/referral-link ``` **Response**: ```json theme={null} { "code": "AbCdEfGh", "url": "https://nano-gpt.com/subscription/AbCdEfGh" } ``` *** ## Error Response Format All errors return JSON in this format: ```json theme={null} { "code": "ERROR_CODE", "message": "Human-readable description", "details": {}, "status": 400 } ``` **Common Error Codes**: | Code | Status | Description | | ---------------- | ------ | ------------------------------------------ | | `UNAUTHORIZED` | 401 | Session required | | `FORBIDDEN` | 403 | Insufficient permissions | | `NOT_FOUND` | 404 | Resource not found | | `CONFLICT` | 409 | Resource conflict (duplicate, wrong state) | | `INVALID_INPUT` | 422 | Validation failed | | `RATE_LIMITED` | 429 | Too many requests | | `INTERNAL_ERROR` | 500 | Server error | *** ## Teams ### List Teams Returns all teams the authenticated user belongs to. ``` GET /api/teams ``` **Response**: ```json theme={null} { "teams": [ { "uuid": "550e8400-e29b-xxxx-xxxx-xxxxxxxxxxxx", "name": "Engineering", "status": "active", "role": "owner" } ] } ``` *** ### Create Team ``` POST /api/teams ``` **Request Body**: ```json theme={null} { "name": "Engineering" } ``` | Field | Type | Required | Description | | ------ | ------ | -------- | ---------------------------------------------------------------- | | `name` | string | Yes | 2-50 characters. Letters, numbers, spaces, hyphens, underscores. | **Response**: ```json theme={null} { "team": { "uuid": "550e8400-e29b-xxxx-xxxx-xxxxxxxxxxxx", "name": "Engineering", "status": "active", "role": "owner" } } ``` **Errors**: * `409 CONFLICT`: You already have a team with this name *** ### Get Team Details ``` GET /api/teams/{teamUuid} ``` **Response**: ```json theme={null} { "team": { "uuid": "550e8400-e29b-xxxx-xxxx-xxxxxxxxxxxx", "name": "Engineering", "status": "active", "paused_at": null, "suspended_at": null, "invite_link_enabled": true, "invite_link_token": "abc123...", "default_member_usage_limit_usd": 100, "usage_limit_usd": null, "responses_retention_days": 14, "usage_limit_enforced": true, "high_spend_text_discount": false, "balances": { "usd_balance": 250.00, "nano_balance": 1500.00 }, "role": "owner" } } ``` **Notes**: * `balances` shows the team owner's account balance * `high_spend_text_discount` indicates whether a high-spend discount is currently active for this team (when active, it applies automatically) * `responses_retention_days` is the optional team default for `/v1/responses` retention (`0..365` or `null`). It is exposed through the Teams API and is returned by team details; it is not currently shown as a visible control in the team Settings UI. * `role` is the requesting user's role in this team *** ### Update Team ``` PATCH /api/teams/{teamUuid} ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "name": "New Team Name", "status": "paused" } ``` | Field | Type | Required | Description | | -------- | ------ | -------- | ---------------------------------- | | `name` | string | No | 2-50 characters | | `status` | string | No | `active`, `paused`, or `suspended` | **Response**: ```json theme={null} { "team": { "uuid": "550e8400-e29b-xxxx-xxxx-xxxxxxxxxxxx", "name": "New Team Name", "status": "paused" } } ``` *** ### Delete Team ``` DELETE /api/teams/{teamUuid} ``` **Required Role**: Owner **Request Body**: ```json theme={null} { "name": "Engineering" } ``` | Field | Type | Required | Description | | ------ | ------ | -------- | ------------------------------------------- | | `name` | string | Yes | Must exactly match team name (confirmation) | **Response**: ```json theme={null} { "ok": true } ``` *** ## Members ### List Members ``` GET /api/teams/{teamUuid}/members ``` **Query Parameters**: | Parameter | Type | Default | Description | | --------- | ------ | ------- | -------------------------- | | `page` | number | 1 | Page number | | `limit` | number | all | Results per page (max 100) | **Response** (with pagination): ```json theme={null} { "members": [ { "sessionId": 12345, "sessionUUID": "abc-123-def", "role": "owner", "joinedAt": "2024-01-15T10:30:00Z", "member_name": "Alice", "displayName": "Alice Smith", "email": "alice@company.com", "usage_limit_usd": 150, "usage_limit_enforced": true, "usage_usd_monthly": 45.50 } ], "pagination": { "page": 1, "limit": 20, "total": 45, "totalPages": 3 } } ``` *** ### Update Member Role ``` PATCH /api/teams/{teamUuid}/members ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "sessionId": 12345, "role": "admin" } ``` | Field | Type | Required | Description | | ----------- | ------ | -------- | ---------------------------------------- | | `sessionId` | number | Yes | Target member's session ID | | `role` | string | Yes | `admin` or `member` (cannot set `owner`) | **Response**: ```json theme={null} { "ok": true } ``` **Errors**: * `403 FORBIDDEN`: Cannot change the owner's role * `400 INVALID_INPUT`: Cannot change your own role *** ### Update Member Usage Limits ``` PATCH /api/teams/{teamUuid}/members ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "sessionId": 12345, "usage_limit_usd": 200, "usage_limit_enforced": true } ``` | Field | Type | Required | Description | | ---------------------- | ------------ | -------- | --------------------------------------------------- | | `sessionId` | number | Yes | Target member's session ID | | `usage_limit_usd` | number\|null | No | Monthly limit in USD, or `null` to use team default | | `usage_limit_enforced` | boolean | No | Hard-enforce the limit (blocks usage when exceeded) | **Response**: ```json theme={null} { "ok": true } ``` *** ### Remove Member ``` DELETE /api/teams/{teamUuid}/members ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "sessionId": 12345 } ``` **Response**: ```json theme={null} { "ok": true } ``` **Errors**: * `403 FORBIDDEN`: Cannot remove the team owner * `400 INVALID_INPUT`: Cannot remove yourself (use `/leave`) *** ### Get Own Preferences Returns the authenticated user's preferences for this team. ``` GET /api/teams/{teamUuid}/members/self ``` **Response**: ```json theme={null} { "bill_to_team": true, "name": "Alice", "usage_limit_usd": 150, "usage_limit_enforced": true, "default_member_usage_limit_usd": 100, "default_usage_limit_enforced": true, "effective_usage_limit_usd": 150, "effective_usage_limit_enforced": true } ``` **Notes**: * `effective_*` fields show the resolved limit (member override or team default) *** ### Update Own Preferences ``` PATCH /api/teams/{teamUuid}/members/self ``` **Request Body**: ```json theme={null} { "bill_to_team": true, "name": "Alice Smith" } ``` | Field | Type | Required | Description | | -------------- | ------- | -------- | -------------------------------------- | | `bill_to_team` | boolean | No | Bill usage to team or personal account | | `name` | string | No | Display name (1-100 characters) | **Response**: ```json theme={null} { "ok": true, "preferences": { "bill_to_team": true, "name": "Alice Smith", "usage_limit_usd": 150, "usage_limit_enforced": true } } ``` *** ### Leave Team ``` POST /api/teams/{teamUuid}/leave ``` **Response**: ```json theme={null} { "ok": true } ``` **Errors**: * `403 FORBIDDEN`: Owner must transfer ownership before leaving *** ## Invitations ### List Pending Invitations ``` GET /api/teams/{teamUuid}/invitations ``` **Required Role**: Owner or Admin **Response**: ```json theme={null} { "invitations": [ { "id": "inv-uuid-123", "email": "bob@company.com", "role": "member", "status": "pending", "token": "abc123...", "created_at": "2024-01-15T10:30:00Z", "expires_at": "2024-01-22T10:30:00Z" } ] } ``` *** ### Send Invitation ``` POST /api/teams/{teamUuid}/invitations ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "email": "bob@company.com", "role": "member" } ``` | Field | Type | Required | Description | | ------- | ------ | -------- | --------------------------------------- | | `email` | string | Yes | Valid email address | | `role` | string | No | `admin` or `member` (default: `member`) | **Response**: ```json theme={null} { "invitation": { "id": "inv-uuid-123", "email": "bob@company.com", "role": "member", "status": "pending", "token": "abc123..." } } ``` *** ### Revoke Invitation ``` PATCH /api/teams/{teamUuid}/invitations ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "action": "revoke", "id": "inv-uuid-123" } ``` | Field | Type | Required | Description | | -------- | ------ | -------- | ------------------------------- | | `action` | string | No | Must be `revoke` (default) | | `id` | string | No\* | Invitation UUID | | `token` | string | No\* | Invitation token (min 16 chars) | \*Provide either `id` or `token` **Response**: ```json theme={null} { "ok": true } ``` *** ### Accept Invitation ``` POST /api/teams/invitations/accept ``` **Request Body**: ```json theme={null} { "token": "abc123def456..." } ``` | Field | Type | Required | Description | | ------- | ------ | -------- | ------------------------------------ | | `token` | string | Yes | Invitation token (min 16 characters) | **Response**: ```json theme={null} { "ok": true } ``` **Errors**: * `409 CONFLICT`: Invitation is not pending * `409 CONFLICT`: Invitation has expired *** ### Lookup Invitation Public endpoint to check invitation details before accepting. ``` GET /api/teams/invitations/lookup?token=abc123... ``` **Authentication**: Not required **Response** (email invitation): ```json theme={null} { "type": "invitation", "email": "bob@company.com", "status": "pending", "teamName": "Engineering" } ``` **Response** (invite link): ```json theme={null} { "type": "link", "teamName": "Engineering", "enabled": true } ``` *** ## Invite Links ### Get Invite Link Status ``` GET /api/teams/{teamUuid}/invite-link ``` **Required Role**: Owner or Admin **Response**: ```json theme={null} { "enabled": true, "token": "abc123def456..." } ``` *** ### Enable/Disable Invite Link ``` POST /api/teams/{teamUuid}/invite-link ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "action": "enable" } ``` | Field | Type | Required | Description | | -------- | ------ | -------- | ------------------------------- | | `action` | string | No | `enable` (default) or `disable` | **Response**: ```json theme={null} { "enabled": true, "token": "abc123def456..." } ``` *** ### Send Invite Link via Email ``` POST /api/teams/{teamUuid}/invite-link/email ``` **Required Role**: Owner or Admin **Rate Limit**: 5 emails per minute **Request Body**: ```json theme={null} { "emails": ["alice@company.com", "bob@company.com"] } ``` | Field | Type | Required | Description | | -------- | --------- | -------- | -------------------------- | | `emails` | string\[] | Yes | 1-10 valid email addresses | **Response**: ```json theme={null} { "ok": true } ``` **Errors**: * `403 FORBIDDEN`: Invite link is disabled * `429 RATE_LIMITED`: Too many emails *** ### Join via Invite Link ``` POST /api/teams/join ``` **Request Body**: ```json theme={null} { "token": "abc123def456..." } ``` **Response**: ```json theme={null} { "ok": true } ``` Or if already a member: ```json theme={null} { "ok": true, "alreadyMember": true } ``` *** ### Cancel Join Request ``` DELETE /api/teams/join ``` **Request Body**: ```json theme={null} { "token": "abc123def456..." } ``` **Response**: ```json theme={null} { "ok": true } ``` *** ## Join Requests ### List Join Requests ``` GET /api/teams/{teamUuid}/join-requests ``` **Required Role**: Owner or Admin **Response**: ```json theme={null} { "requests": [ { "id": "req-uuid-123", "user_id": 12345, "status": "pending", "created_at": "2024-01-15T10:30:00Z", "name": "Charlie", "email": "charlie@example.com" } ] } ``` *** ### Accept/Reject Join Request ``` PATCH /api/teams/{teamUuid}/join-requests ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "action": "accept", "id": "req-uuid-123" } ``` | Field | Type | Required | Description | | -------- | ------ | -------- | ------------------------------ | | `action` | string | No | `accept` (default) or `reject` | | `id` | string | Yes | Join request UUID | **Response**: ```json theme={null} { "ok": true } ``` *** ### Delete Join Request Delete a processed (non-pending) join request. ``` DELETE /api/teams/{teamUuid}/join-requests ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "id": "req-uuid-123" } ``` **Response**: ```json theme={null} { "ok": true } ``` **Errors**: * `409 CONFLICT`: Cannot delete a pending request (must accept/reject first) *** ## Usage & Billing ### Get Team Usage ``` GET /api/teams/{teamUuid}/usage ``` **Query Parameters**: | Parameter | Type | Default | Description | | --------- | ------ | ------- | ----------------------- | | `from` | string | - | Start date (ISO format) | | `to` | string | - | End date (ISO format) | **Response**: ```json theme={null} { "byActor": [ { "actorSessionId": 12345, "displayName": "Alice Smith", "totalAmount": 45.50, "currency": "USD" }, { "actorSessionId": 12346, "displayName": "Bob Jones", "totalAmount": 32.25, "currency": "USD" } ], "totals": [ { "totalAmount": 77.75, "currency": "USD" } ] } ``` *** **Notes**: * Team-billed usage is charged against the team's balances (shown on `GET /api/teams/{teamUuid}`). * Individual members can choose whether to bill to the team or their personal account via `PATCH /api/teams/{teamUuid}/members/self` (`bill_to_team`). *** ### High-Spend Text Discount Some teams may automatically qualify for discounted pricing on text model usage. When active, `GET /api/teams/{teamUuid}` will show: ```json theme={null} { "team": { "high_spend_text_discount": true } } ``` ## Settings ### Update Team Settings ``` PATCH /api/teams/{teamUuid}/settings ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "default_member_usage_limit_usd": 100, "responses_retention_days": 14, "usage_limit_enforced": true } ``` | Field | Type | Required | Description | | -------------------------------- | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `default_member_usage_limit_usd` | number\|null | No | Default monthly limit for members | | `team_usage_limit_usd` | number\|null | No | Team-wide spending limit | | `responses_retention_days` | number\|null | No | Team default retention for `/v1/responses` (`0..365`). Use `null` to clear. API-exposed; not currently shown in the team Settings UI | | `usage_limit_enforced` | boolean | No | Hard-enforce limits | **Response**: ```json theme={null} { "ok": true } ``` *** ## BYOK (Team Settings & Provider Keys) Teams can store provider keys and configure how team-billed traffic uses BYOK. For provider slugs and key formats (including JSON-based credentials like AWS/Azure), see `api-reference/miscellaneous/byok`. **Rate Limits**: * Key management (list/add/revoke): 5 operations per minute per team * Key validation: 10 requests per minute per team ### Get BYOK Settings ``` GET /api/teams/{teamUuid}/byok-settings ``` **Authorization**: Any team member **Response**: ```json theme={null} { "byok_enabled": true, "byok_mode": "prefer_team" } ``` BYOK modes: | Mode | Behavior | | -------------- | ------------------------------------------------- | | `disabled` | Team BYOK is off | | `prefer_team` | Use team keys when available; otherwise fall back | | `require_team` | Require team keys; fail if missing | ### Update BYOK Settings ``` PATCH /api/teams/{teamUuid}/byok-settings ``` **Authorization**: Owner or Admin **Request Body**: ```json theme={null} { "byok_enabled": true, "byok_mode": "prefer_team" } ``` **Response**: ```json theme={null} { "ok": true, "byok_enabled": true, "byok_mode": "prefer_team" } ``` ### List Team Provider Keys ``` GET /api/teams/{teamUuid}/provider-keys ``` **Authorization**: Any team member **Response**: ```json theme={null} { "keys": [ { "id": 42, "provider": "openai", "key_suffix": "abcd", "status": "active", "created_at": "2025-01-15T10:00:00Z", "last_used_at": "2025-01-20T14:30:00Z", "added_by_session_id": 123 } ] } ``` ### Add or Replace a Team Provider Key ``` POST /api/teams/{teamUuid}/provider-keys ``` **Authorization**: Owner or Admin **Request Body**: ```json theme={null} { "provider": "openai", "key": "sk-..." } ``` **Response**: ```json theme={null} { "success": true, "id": 43, "provider": "openai", "key_suffix": "abcd" } ``` ### Revoke a Team Provider Key ``` DELETE /api/teams/{teamUuid}/provider-keys?provider=openai ``` **Authorization**: Owner or Admin **Response**: ```json theme={null} { "success": true } ``` ### Validate a Team Provider Key (Optional Preflight) ``` POST /api/teams/{teamUuid}/provider-keys/validate ``` **Authorization**: Owner or Admin **Request Body**: ```json theme={null} { "provider": "openai", "key": "sk-...", "model": "gpt-4o-mini" } ``` **Response**: ```json theme={null} { "valid": true, "model": "gpt-4o-mini" } ``` Or on failure: ```json theme={null} { "valid": false, "error": "Invalid API key", "model": "gpt-4o-mini" } ``` **Notes**: * In `prefer_team` mode, team-billed traffic will not use a member's personal BYOK keys unless the client explicitly enables BYOK for the request. *** ## Model Access Control ### Get Allowed Models ``` GET /api/teams/{teamUuid}/allowed-models ``` **Response**: ```json theme={null} { "allowed_models": { "claude-sonnet-4-5": true, "gpt-5-1": true, "claude-opus-4-5": false }, "all_allowed": false } ``` Or if all models are allowed: ```json theme={null} { "allowed_models": null, "all_allowed": true } ``` *** ### Update Allowed Models ``` PATCH /api/teams/{teamUuid}/allowed-models ``` **Required Role**: Owner or Admin **Request Body**: ```json theme={null} { "allowed_models": { "claude-sonnet-4-5": true, "gpt-5-1": true, "claude-opus-4-5": false } } ``` To allow all models: ```json theme={null} { "allowed_models": null } ``` | Field | Type | Required | Description | | ---------------- | ------------ | -------- | ----------------------------------------------- | | `allowed_models` | object\|null | Yes | Map of model keys to boolean, or `null` for all | **Response**: ```json theme={null} { "ok": true, "allowed_models": { "claude-sonnet-4-5": true, "gpt-5-1": true, "claude-opus-4-5": false }, "all_allowed": false } ``` **Notes**: * `allowed_models: null` means all models are allowed. * When `allowed_models` is an object, only models with a value of `true` are allowed for non-owners. Any missing models (or models with `false`) are blocked. * An empty object `{}` blocks all models for non-owners. * Team owners are not restricted by the allowlist. *** ## Ownership ### Transfer Ownership ``` POST /api/teams/{teamUuid}/owner ``` **Required Role**: Owner **Request Body**: ```json theme={null} { "sessionId": 12345 } ``` | Field | Type | Required | Description | | ----------- | ------ | -------- | ---------------------- | | `sessionId` | number | Yes | New owner's session ID | **Response**: ```json theme={null} { "ok": true } ``` **Side Effects**: * Current owner becomes admin * Target member becomes owner **Errors**: * `409 CONFLICT`: Target is already the owner * `400 INVALID_INPUT`: Cannot transfer ownership to yourself *** ## Role Reference | Feature | Owner | Admin | Member | | ---------------------------------------- | ----- | ----- | ------ | | View team details | Yes | Yes | Yes | | Update team (name/status) | Yes | Yes | No | | Delete team | Yes | No | No | | List members | Yes | Yes | Yes | | Change member roles | Yes | Yes | No | | Set member usage limits | Yes | Yes | No | | Remove members | Yes | Yes | No | | Update own preferences (`/members/self`) | Yes | Yes | Yes | | Manage invitations | Yes | Yes | No | | Manage invite link | Yes | Yes | No | | Send invite link email | Yes | Yes | No | | View join requests | Yes | Yes | No | | Accept/reject join requests | Yes | Yes | No | | Delete processed join requests | Yes | Yes | No | | View team usage (`/usage`) | Yes | Yes | Yes | | Update team settings (`/settings`) | Yes | Yes | No | | View allowed models | Yes | Yes | Yes | | Update allowed models | Yes | Yes | No | | Transfer ownership | Yes | No | No | | View BYOK settings | Yes | Yes | Yes | | Update BYOK settings | Yes | Yes | No | | List team provider keys | Yes | Yes | Yes | | Add/revoke team provider keys | Yes | Yes | No | | Validate team provider keys | Yes | Yes | No | *** ## Rate Limits | Endpoint | Limit | | ---------------------------------------------- | ------------------- | | `POST /api/teams/{teamUuid}/invite-link/email` | 5 emails per minute | *** ## Webhooks (Coming Soon) Future webhook events: * `team.member.joined` * `team.member.removed` * `team.usage.limit_reached` * `team.status.changed` # TEE Verification Source: https://docs.nano-gpt.com/api-reference/tee-verification Guide to verifying TEE attestation reports and signatures for TEE-backed models. ## Overview NanoGPT supports TEE-backed models with attestation/signature verification for stronger integrity and data-in-use isolation. Confidentiality/logging outcomes are provider-specific, and plaintext may still exist at gateway/proxy layers outside the enclave depending on transport and provider architecture. Use these endpoints to verify enclave attestation and signatures for chat completions. ### Fetch Attestation Report ```bash theme={null} curl "https://nano-gpt.com/api/v1/tee/attestation?model=TEE/llama-3.3-70b-instruct" \ -H "Authorization: Bearer $API_KEY" ``` ### Fetch ECDSA Signature ```bash theme={null} curl "https://nano-gpt.com/api/v1/tee/signature/{requestId}?model=TEE/llama-3.3-70b-instruct&signing_algo=ecdsa" \ -H "Authorization: Bearer $API_KEY" ``` ## Python Example Save the following as `test_tee.py` and run: ```bash theme={null} python3 test_tee.py --api-key YOUR_API_KEY --base-url https://nano-gpt.com --model TEE/llama-3.3-70b-instruct ``` ```python theme={null} #!/usr/bin/env python3 """ TEE Attestation & Signature Verification Example for NanoGPT. """ import requests import json import hashlib from cryptography import x509 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend import base64 try: from eth_account.messages import encode_defunct from eth_account.account import Account except ImportError: raise ImportError("Please install eth_account: pip install eth-account") import os from cryptography.x509.oid import ObjectIdentifier # if needed # Hardcoded configuration - set your values here or even better use env vars! API_KEY = "INSERT_API_KEY" BASE_URL = "http://nano-gpt.com/api" # Production base URL MODEL = "TEE/llama-3.3-70b-instruct" def fetch_attestation(base_url, api_key, model): url = f"{base_url}/v1/tee/attestation" headers = {"Authorization": f"Bearer {api_key}"} # generate challenge nonce to prevent replay attacks nonce = base64.b64encode(os.urandom(16)).decode() params = {"model": model, "nonce": nonce} resp = requests.get(url, headers=headers, params=params) print("=== Attestation Report ===") print(f"Status: {resp.status_code}") try: data = resp.json() print(json.dumps(data, indent=2)) except ValueError: print(resp.text) data = None # verify the nonce was echoed back if data and data.get("nonce") != nonce: raise Exception(f"Nonce mismatch! Expected {nonce}, got {data.get('nonce')}") print("Nonce successfully verified.") return data def chat_completion(base_url, api_key, model, stream=False): url = f"{base_url}/v1/chat/completions" headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } payload = { "model": model, "stream": stream, "messages": [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is your model name?"} ] } if stream: resp = requests.post(url, headers=headers, json=payload, stream=True) print("=== Streaming Chat Completion ===") request_id = None for line in resp.iter_lines(decode_unicode=True): if line.startswith("data: "): data_str = line[len("data: "):] if data_str == "[DONE]": print("[DONE]") break chunk = json.loads(data_str) print(json.dumps(chunk, indent=2)) if not request_id and "id" in chunk: request_id = chunk["id"] return request_id else: payload_str = json.dumps(payload, separators=(",", ":"), ensure_ascii=False) req_hash = hashlib.sha256(payload_str.encode("utf-8")).hexdigest() resp = requests.post(url, headers=headers, json=payload) resp_text = resp.text resp_hash = hashlib.sha256(resp_text.encode("utf-8")).hexdigest() print("=== Chat Completion ===") print("Request Payload Hash:", req_hash) print(resp_text) print("Response Body Hash:", resp_hash) result = json.loads(resp_text) return {"id": result.get("id"), "request_hash": req_hash, "response_hash": resp_hash} def fetch_signature(base_url, api_key, model, request_id, algo="ecdsa"): url = f"{base_url}/v1/tee/signature/{request_id}" headers = {"Authorization": f"Bearer {api_key}"} params = {"model": model, "signing_algo": algo} resp = requests.get(url, headers=headers, params=params) print("=== Signature ===") print(f"Status: {resp.status_code}") try: data = resp.json() print(json.dumps(data, indent=2)) except ValueError: print(resp.text) data = None return data def verify_signature(signing_address, message, signature_hex): print("=== Verifying Signature ===") # Use eth_account for ECDSA recovery with Ethereum prefixed message signature = signature_hex if signature_hex.startswith("0x") else "0x" + signature_hex msg = encode_defunct(text=message) recovered = Account.recover_message(msg, signature=signature) if recovered.lower() == signing_address.lower(): print("Signature verification succeeded.") else: raise Exception(f"Signature verification failed: expected address {signing_address}, got {recovered}") def verify_nvidia_attestation(nvidia_payload): print("=== Verifying NVIDIA Attestation via NVIDIA Attestation Service ===") # ensure payload is a dict payload_json = json.loads(nvidia_payload) if isinstance(nvidia_payload, str) else nvidia_payload url = "https://nras.attestation.nvidia.com/v3/attest/gpu" headers = {"accept": "application/json", "content-type": "application/json"} resp = requests.post(url, headers=headers, json=payload_json) print(f"Status: {resp.status_code}") try: result = resp.json() print(json.dumps(result, indent=2)) except ValueError: print(resp.text) result = None if resp.status_code != 200: raise Exception(f"NVIDIA attestation verification failed with status {resp.status_code}") print("NVIDIA attestation verified successfully.") # Helpers for enclave identity pinning def verify_enclave_identity(reported, expected, label): print(f"=== Verifying {label} ===") if not reported or not expected: raise Exception(f"Missing reported or expected {label}") if reported.lower() != expected.lower(): raise Exception(f"{label} mismatch: expected {expected}, got {reported}") print(f"{label} matches expected value.") def parse_nvidia_measurement(nvidia_payload): payload = json.loads(nvidia_payload) if isinstance(nvidia_payload, str) else nvidia_payload if "evidence" in payload: b64 = payload["evidence"] elif payload.get("evidence_list") and "evidence" in payload["evidence_list"][0]: b64 = payload["evidence_list"][0]["evidence"] else: raise Exception("no evidence field for NVIDIA payload") q = base64.b64decode(b64) body = q[48:48+384] return body[48:80].hex(), body[112:144].hex() def parse_intel_measurement(intel_quote): qb = bytes.fromhex(intel_quote) body = qb[48:48+384] return body[48:80].hex(), body[112:144].hex() def main(): # will hold cryptography public key object for verification pub_key_obj = None attestation = fetch_attestation(BASE_URL, API_KEY, MODEL) # Full attestation-report verification: NVIDIA payload if attestation and "nvidia_payload" in attestation: try: verify_nvidia_attestation(attestation["nvidia_payload"]) except Exception as e: raise Exception(f"NVIDIA payload verification error: {e}") # Intel TDX quote verification instructions if attestation and "intel_quote" in attestation: print("=== Intel TDX Quote Verification ===") intel_quote = attestation["intel_quote"] print("To verify the Intel TDX quote, go to https://proof.t16z.com, paste the quote below, and follow the instructions:") print(intel_quote) # Enclave identity pinning against published measurements print("=== Enclave Identity Pinning ===") # NVIDIA measurement exp_nm = '00000000020133000130008048dfd18fe229bf16eb9d30cca0f11a24dafe6eb7' exp_ns = '3000ec80d19d78143549a41d62d0078c56b2d538ed5d394f19e5af7d93bd1a24' rep_nm, rep_ns = parse_nvidia_measurement(attestation["nvidia_payload"]) verify_enclave_identity(rep_nm, exp_nm, "NVIDIA MRENCLAVE") verify_enclave_identity(rep_ns, exp_ns, "NVIDIA MRSIGNER") # Intel measurement exp_im = '6489d6c8e4f92f160b7cad34207b00c100000000000000000000000000000000' exp_is = '00000000000000000000001000000000e702060000000000924f8a6140332b2c' rep_im, rep_is = parse_intel_measurement(attestation.get("intel_quote", "")) verify_enclave_identity(rep_im, exp_im, "Intel MRENCLAVE") verify_enclave_identity(rep_is, exp_is, "Intel MRSIGNER") # Extract raw EC public key from certificate in the attestation report public_key = None if attestation and "nvidia_payload" in attestation: try: # parse nvidia_payload JSON string if needed payload = attestation["nvidia_payload"] if isinstance(payload, str): payload = json.loads(payload) # certificate is base64-encoded PEM; decode to get actual PEM bytes cert_b64 = payload["evidence_list"][0]["certificate"] pem_data = base64.b64decode(cert_b64) cert_obj = x509.load_pem_x509_certificate(pem_data, default_backend()) # get cryptography public key object pub_key_obj = cert_obj.public_key() # serialize for debugging if needed public_key = pub_key_obj.public_bytes( encoding=serialization.Encoding.X962, format=serialization.PublicFormat.UncompressedPoint ).hex() except Exception as e: print("Failed to extract public key from certificate:", e) public_key = None # get enclave signing address from attestation signing_address = attestation.get("signing_address") if not signing_address: raise Exception("Attestation response missing 'signing_address'") # Non-streaming chat and signature verification chat_data = chat_completion(BASE_URL, API_KEY, MODEL, stream=False) if chat_data and isinstance(chat_data, dict) and "id" in chat_data: request_id = chat_data["id"] signature_data = fetch_signature(BASE_URL, API_KEY, MODEL, request_id) if signature_data and "text" in signature_data and "signature" in signature_data: # ensure signing_address matches the attested one if signature_data.get("signing_address") and signature_data["signing_address"].lower() != signing_address.lower(): raise Exception(f"Unexpected signing_address from signature: {signature_data['signing_address']}") message = signature_data["text"] signature = signature_data["signature"] verify_signature(signing_address, message, signature) # Streaming chat and signature verification (no local hashing) stream_id = chat_completion(BASE_URL, API_KEY, MODEL, stream=True) if stream_id: signature_data = fetch_signature(BASE_URL, API_KEY, MODEL, stream_id) if signature_data and "text" in signature_data and "signature" in signature_data: if signature_data.get("signing_address") and signature_data["signing_address"].lower() != signing_address.lower(): raise Exception(f"Unexpected signing_address from signature: {signature_data['signing_address']}") message = signature_data["text"] signature = signature_data["signature"] verify_signature(signing_address, message, signature) if __name__ == "__main__": main() ``` # Text Generation Source: https://docs.nano-gpt.com/api-reference/text-generation Complete guide to text generation APIs ## Overview The NanoGPT API offers multiple ways to generate text, including OpenAI-compatible endpoints and our legacy options. This guide covers all available text generation methods. If you are using a TEE-backed model (e.g., prefixed with `TEE/`), you can also verify the enclave attestation and signatures for your chat completions. See the [TEE Model Verification guide](/api-reference/tee-verification) for more details. For authenticated API-key requests, you can opt in to a paid input safety preflight by sending the `moderation` header. See [Inline Moderation](/api-reference/miscellaneous/inline-moderation) for supported text routes, model selection, billing behavior, and error codes. ## Provider Selection Provider selection is available for supported open-source models. `X-Provider` explicitly selects a provider for the request and is always billed pay-as-you-go at the selected provider's price, including provider-selection markup. For subscription users, sending `X-Provider` bypasses subscription coverage for that request; `X-Billing-Mode: paygo` is only needed when forcing pay-as-you-go without an explicit provider or when saved provider preferences should apply to subscription-included traffic. See [Provider Selection](/api-reference/miscellaneous/provider-selection) and [Pay-As-You-Go Billing Override](/api-reference/miscellaneous/billing-override). For one-off routing preferences, append a suffix to eligible model IDs: * `:fast` / `:speed` for fastest estimated completion * `:cheap` / `:price` / `:floor` for cheapest provider * `:throughput` for highest TPS * `:latency` for lowest TTFT * `:tools` for tools-capable routing See [Model Suffixes](/api-reference/miscellaneous/model-suffixes) for the complete suffix list and conflict rules. ## OpenAI Compatible Endpoints ### Chat Completions (v1/chat/completions) This endpoint mimics OpenAI's chat completions API: For high-volume offline workloads where latency is not important, use the [Batch API](/api-reference/endpoint/batches) to upload JSONL chat completion requests and process them asynchronously. ```python theme={null} import requests import json BASE_URL = "https://nano-gpt.com/api/v1" API_KEY = "YOUR_API_KEY" # Replace with your API key headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "Accept": "text/event-stream" # Required for SSE streaming } def stream_chat_completion(messages, model="openai/gpt-5.6-sol"): """ Send a streaming chat completion request using the OpenAI-compatible endpoint. """ data = { "model": model, "messages": messages, "stream": True # Enable streaming } response = requests.post( f"{BASE_URL}/chat/completions", headers=headers, json=data, stream=True ) if response.status_code != 200: raise Exception(f"Error: {response.status_code}") for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith('data: '): line = line[6:] if line == '[DONE]': break try: chunk = json.loads(line) if chunk['choices'][0]['delta'].get('content'): yield chunk['choices'][0]['delta']['content'] except json.JSONDecodeError: continue # Example usage messages = [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Please explain the concept of artificial intelligence."} ] try: print("Assistant's Response:") for content_chunk in stream_chat_completion(messages): print(content_chunk, end='', flush=True) print("") except Exception as e: print(f"Error: {str(e)}") ``` ### Responses (v1/responses) Use the OpenAI Responses-compatible endpoint for stateful threading (`previous_response_id`), background processing, and Responses-style streaming events. See the dedicated docs at [/api-reference/endpoint/responses](/api-reference/endpoint/responses). ```python theme={null} import requests BASE_URL = "https://nano-gpt.com/api/v1" API_KEY = "YOUR_API_KEY" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } data = { "model": "openai/gpt-5.6-sol", "input": "Summarize the Responses API in one sentence." } response = requests.post( f"{BASE_URL}/responses", headers=headers, json=data ) response.raise_for_status() print(response.json()) ``` ### Direct Web Search (api/web) Use `POST /api/web` when you need direct search control instead of chat orchestration: * Explicit `query` payload control * Linkup output types: `searchResults`, `sourcedAnswer`, `structured` * Date and domain filters (`fromDate`, `toDate`, `includeDomains`, `excludeDomains`) See [Direct Web Search API](/api-reference/endpoint/web-search) for request/response schema, pricing, and error codes. ### Text Completions (v1/completions) This endpoint mimics OpenAI's legacy text completions API: `POST /api/v1/completions` is best effort. Performance and compatibility may be less consistent than `POST /api/v1/chat/completions` because some upstream providers do not support the legacy completions API. ```python theme={null} import requests import json BASE_URL = "https://nano-gpt.com/api/v1" API_KEY = "YOUR_API_KEY" # Replace with your API key headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } def get_completion(prompt, model="openai/gpt-5.6-sol"): """ Send a text completion request using the OpenAI-compatible endpoint. """ data = { "model": model, "prompt": prompt, "max_tokens": 1000, # Optional: maximum number of tokens to generate "temperature": 0.7, # Optional: controls randomness (0-2) "top_p": 1, # Optional: nucleus sampling parameter "stream": False # Set to True for streaming responses } response = requests.post( f"{BASE_URL}/completions", headers=headers, json=data ) if response.status_code != 200: raise Exception(f"Error: {response.status_code}") return response.json() # Example usage prompt = "Write a short story about a robot learning to paint:" try: response = get_completion(prompt) print("Completion:", response['choices'][0]['text']) except Exception as e: print(f"Error: {str(e)}") ``` ## Legacy Text Completions For the older, non-OpenAI compatible endpoint: ```python theme={null} import requests import json BASE_URL = "https://nano-gpt.com/api" API_KEY = "YOUR_API_KEY" headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } def talk_to_gpt(prompt, model="openai/gpt-5.6-sol", messages=[]): data = { "prompt": prompt, "model": model, "messages": messages } response = requests.post(f"{BASE_URL}/talk-to-gpt", headers=headers, json=data) return response.text if response.status_code == 200 else None # Example usage messages = [ {"role": "user", "content": "Hello, how are you?"}, {"role": "assistant", "content": "I'm doing well, thank you! How can I assist you today?"} ] prompt = "Please explain the concept of artificial intelligence." response = talk_to_gpt(prompt, messages=messages) if response: # Split the response to separate the text and NanoGPT info parts = response.split('') text_response = parts[0].strip() nano_info = json.loads(parts[1].split('')[0]) print("NanoGPT Response:", text_response) print("Cost:", nano_info['cost']) print("Input Tokens:", nano_info['inputTokens']) print("Output Tokens:", nano_info['outputTokens']) else: print("Failed to get response from GPT") ``` ### Caching (Implicit and Explicit Controls) For the full guide (supported models, thresholds, pricing, and usage fields), see [Prompt Caching](/api-reference/miscellaneous/prompt-caching). NanoGPT automatically applies implicit caching on providers/models that support it (including OpenAI, Gemini, and many open-source provider/model routes), so most requests do not need caching flags. Set top-level `caching: true` or append `:caching` / `:cache` / `:cached` to the model when you want NanoGPT to route the request to any available provider that supports prompt/input caching. This is capability-based routing: you do not need to choose a provider. If no cache-capable provider is available for the model, the request fails rather than silently using a non-caching provider. Use explicit prompt-caching controls (`prompt_caching`, `promptCaching`, and body-level `cache_control` alias, plus inline `cache_control`) when you need Claude-specific cache boundaries, TTL selection, or `prompt_caching.stickyProvider` consistency control. Top-level `caching: true` does not add Anthropic-style `cache_control` markers or configure cache TTLs. #### Cache-Capable Provider Routing ```json theme={null} { "model": "model-id", "caching": true, "messages": [ { "role": "user", "content": "Hello" } ] } ``` By default, `caching: true` enables sticky provider routing. After the first successful matching request, NanoGPT will try to use the same provider for later matching requests from the same API key or session, improving the chance of provider-side cache hits. This does not guarantee that a request will be served from cache. To require a cache-capable provider without sticky routing, set `stickyprovider: false`: ```json theme={null} { "model": "model-id", "caching": true, "stickyprovider": false, "messages": [ { "role": "user", "content": "Hello" } ] } ``` Top-level `stickyProvider` is also accepted as a camelCase alias for `stickyprovider`. Equivalent model suffix: ```json theme={null} { "model": "moonshotai/kimi-k2.6:thinking:caching", "messages": [ { "role": "user", "content": "Hello" } ] } ``` For `caching: true`, NanoGPT filters to available, non-excluded, prompt-caching-capable providers; prefers the recorded sticky provider when enabled and still usable; otherwise chooses the cheapest cache-capable provider by base input + output price. Cache write/read pricing is used only as a tie-breaker. The `prompt_caching` / `promptCaching` helper accepts these options: | Parameter | Type | Default | Description | | -------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `enabled` | boolean | — | Enable prompt caching | | `ttl` | string | `"5m"` | Cache time-to-live: `"5m"` or `"1h"` | | `cut_after_message_index` / `cutAfterMessageIndex` | integer | — | Zero-based index; cache all messages up to and including this index | | `stickyProvider` | boolean | `false` | When `true`, disable automatic failover to preserve explicit prompt-cache consistency. Returns 503 error instead of switching services. | #### Explicit `cache_control` markers ```python theme={null} def chat_completion_with_prompt_cache(messages, model="anthropic/claude-sonnet-4.5"): """ Attach cache_control directly to the static prompt blocks you want reused. """ headers_with_cache = {**headers} # reuse Authorization + Content-Type from above payload = { "model": model, "messages": messages, "temperature": 0.7, "max_tokens": 4000, "stream": False } response = requests.post( f"{BASE_URL}/chat/completions", headers=headers_with_cache, json=payload ) response.raise_for_status() return response.json() messages_with_breakpoint = [ { "role": "system", "content": [ { "type": "text", "text": "You are a financial watchdog. Answer in JSON with rationale fields.", "cache_control": {"type": "ephemeral", "ttl": "5m"} } ] }, { "role": "user", "content": [ { "type": "text", "text": ( "Context: <10 kB of policy + rubric that rarely changes>\n" "A separate uncached message will carry the live question." ), "cache_control": {"type": "ephemeral", "ttl": "5m"} } ] }, {"role": "user", "content": "What risks should I watch for in today's filing?"} ] result = chat_completion_with_prompt_cache(messages_with_breakpoint) print(result["choices"][0]["message"]["content"]) ``` * `cache_control` belongs to individual content blocks (`system`, `user`, tool definitions, etc.). Each marker caches the entire prefix up to and including that block. * Supported explicit TTLs are `5m` and `1h` (Claude flows). Omit `ttl` to use the default `5m` window. * `anthropic-beta: prompt-caching-2024-07-31` is supported for compatibility and required for Anthropic-native Claude caching flows. * For implicit-caching providers, no explicit `cache_control` markers are required. * Check `usage.prompt_tokens_details.cached_tokens` in NanoGPT's response to confirm what was billed at the discounted rate. #### Using the `prompt_caching` helper If you prefer not to duplicate `cache_control` entries manually, NanoGPT accepts a helper object that tags the leading prefix for you. ```python theme={null} payload = { "model": "anthropic/claude-opus-4.5", "messages": [ {"role": "system", "content": "Summaries must be under 100 words."}, {"role": "user", "content": "Cache the playbook for an hour."}, {"role": "user", "content": "Live question goes here"} ], "prompt_caching": { "enabled": True, "ttl": "1h", "cut_after_message_index": 1 # cache everything through message index 1 } } requests.post( f"{BASE_URL}/chat/completions", headers={ **headers, "anthropic-beta": "prompt-caching-2024-07-31" }, json=payload ) ``` `cut_after_message_index` is zero-based and points at the last message in the static prefix. NanoGPT will attach a `cache_control` block with your TTL to each message up to that index before forwarding the request upstream. If you omit `cut_after_message_index`, NanoGPT will select a cache boundary automatically; set it explicitly if you need full control. If you need different cache durations or non-contiguous breakpoints, fall back to explicit `cache_control` markers in your `messages` array. ### Explicit Prompt Cache Consistency NanoGPT automatically fails over to backup services when the primary service is temporarily unavailable. While this ensures high availability, it can break your prompt cache because **each backend service maintains its own separate cache**. If cache consistency is more important than availability for your use case, you can enable the `stickyProvider` option: ```json theme={null} { "model": "anthropic/claude-sonnet-4.5", "messages": [...], "prompt_caching": { "enabled": true, "ttl": "5m", "stickyProvider": true } } ``` **Behavior:** * **`stickyProvider: false` (default)** — If the primary service fails, NanoGPT automatically retries with a backup service. Your request succeeds, but the cache may be lost (you'll pay full price for that request and need to rebuild the cache). * **`stickyProvider: true`** — If the primary service fails, NanoGPT returns a 503 error instead of failing over. Your cache remains intact for when the service recovers. **When to use `stickyProvider: true`:** * You have very large cached contexts where cache misses are expensive * You prefer to retry failed requests yourself rather than pay for cache rebuilds * Cost predictability is more important than request success rate **When to use `stickyProvider: false` (default):** * You prefer requests to always succeed when possible * Occasional cache misses are acceptable * You're using shorter contexts where cache rebuilds are inexpensive **Error response when stickyProvider blocks a failover:** ```json theme={null} { "error": { "message": "Service is temporarily unavailable. Fallback disabled to preserve prompt cache consistency. Switching services would invalidate your cached tokens. Remove stickyProvider option or retry later.", "status": 503, "type": "service_unavailable", "code": "fallback_blocked_for_cache_consistency" } } ``` ### Chat Completions with Web Search Enable real-time web access for any model by appending special suffixes: ```python theme={null} def chat_completion_with_web_search(messages, model="openai/gpt-5.6-sol", search_depth="standard"): """ Send a chat completion request with web search enabled. Args: messages: List of message objects model: Base model name search_depth: "standard" ($0.006) or "deep" ($0.06) """ # Append the appropriate suffix for web search if search_depth == "deep": model_with_search = f"{model}:online/linkup-deep" else: model_with_search = f"{model}:online" data = { "model": model_with_search, "messages": messages, "stream": True } response = requests.post( f"{BASE_URL}/chat/completions", headers=headers, json=data, stream=True ) if response.status_code != 200: raise Exception(f"Error: {response.status_code}") for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith('data: '): line = line[6:] if line == '[DONE]': break try: chunk = json.loads(line) if chunk['choices'][0]['delta'].get('content'): yield chunk['choices'][0]['delta']['content'] except json.JSONDecodeError: continue # Example: Get current information messages = [ {"role": "user", "content": "What happened in the tech industry this week?"} ] print("Standard web search:") for content in chat_completion_with_web_search(messages): print(content, end='', flush=True) # Example: Deep research research_messages = [ {"role": "user", "content": "Provide a comprehensive analysis of the latest developments in quantum computing"} ] print("\n\nDeep web search:") for content in chat_completion_with_web_search(research_messages, search_depth="deep"): print(content, end='', flush=True) ``` #### Web Search Options * **`:online`** - Standard search with 10 results (\$0.006 per request) * **`:online/linkup-deep`** - Deep iterative search (\$0.06 per request) For the full provider-specific suffix list, including `:online/sofya`, `:online/exa-instant`, `:online/exa-deep-reasoning`, `:online/brave`, and `:online/valyu-web-deep`, see [Model Suffixes](/api-reference/miscellaneous/model-suffixes#web-search-suffixes). Web search dramatically improves factuality - Gemini 3 Flash Preview with web access shows a 10x improvement in accuracy, making it twice as accurate as non-web baselines. For direct `/api/web` usage with structured output, domain/date filters, and explicit query control, see [Direct Web Search API](/api-reference/endpoint/web-search). # Text-to-Speech (TTS) Source: https://docs.nano-gpt.com/api-reference/text-to-speech Complete guide to text-to-speech synthesis APIs ## Overview The NanoGPT TTS API allows you to convert text into natural-sounding speech using various models from different providers. The API supports multiple languages, voices, and customization options including speed control, voice instructions, and audio format selection. For voice cloning (create a reusable custom voice from a reference clip), see `api-reference/endpoint/voice-cloning.mdx`. ## Music generation NanoGPT also supports text-to-music generation via the OpenAI-compatible `POST /v1/audio/speech` endpoint when you use a music model. See `api-reference/music-generation.mdx`. ## Available Models * **Kokoro-82m**: High-quality multilingual model with 44 voices (\$0.001/1k chars) * **Elevenlabs-Turbo-V2.5**: Premium quality with 46 voices and style controls (\$0.06/1k chars) * **Elevenlabs-V3**: ElevenLabs expressive model family * **tts-1**: OpenAI's standard quality model with low latency (\$0.015/1k chars) * **tts-1-hd**: OpenAI's high definition model (\$0.030/1k chars) * **gpt-4o-mini-tts**: Ultra-low cost OpenAI model (\$0.0006/1k chars) * **MiniMax Speech models**: Supports cloned voices via custom voice IDs * **Qwen-3-TTS-1.7B**: Supports cloned voices via speaker embeddings ## Streaming support in `POST /v1/audio/speech` Set `stream: true` to request chunked audio streaming. Default remains buffered (`stream: false`). | Model | Provider | Streaming Support | | ----------------------- | -------------------- | ----------------- | | `tts-1` | OpenAI | Yes | | `tts-1-hd` | OpenAI | Yes | | `gpt-4o-mini-tts` | OpenAI | Yes | | `Elevenlabs-Turbo-V2.5` | ElevenLabs (via FAL) | Yes | | `Elevenlabs-V3` | ElevenLabs (via FAL) | Yes | Other TTS models (for example Kokoro, Gemini, Qwen, MiniMax, Inworld) ignore `stream` and return buffered audio. ## Authentication All requests require authentication via API key: ```http theme={null} x-api-key: YOUR_API_KEY ``` ## Synchronous vs Asynchronous * Synchronous: `POST /v1/audio/speech` returns audio bytes directly. Best for UI playback and short prompts. See `api-reference/endpoint/speech.mdx`. * Asynchronous: `POST /tts` returns a ticket; poll `GET /tts/status` for completion. Best for long audio, batch jobs, and webhook workflows. | Aspect | v1/audio/speech (sync) | TTS job flow (async) | | --------- | ---------------------------------- | --------------------------- | | Trigger | Request/response | Submit + poll/webhook | | Latency | Low | Higher (queue + processing) | | Max input | Shorter | Larger payloads | | Streaming | `stream: true` on supported models | Not applicable | ## Basic Text-to-Speech ### Simple TTS Request ```python theme={null} import requests import json BASE_URL = "https://nano-gpt.com/api" API_KEY = "YOUR_API_KEY" def text_to_speech(text, model="Kokoro-82m", voice=None, **kwargs): """ Convert text to speech using NanoGPT TTS API """ headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } payload = { "text": text, "model": model } if voice: payload["voice"] = voice # Add any additional parameters payload.update(kwargs) response = requests.post( f"{BASE_URL}/tts", headers=headers, json=payload ) if response.status_code == 200: # Check if response is JSON or binary content_type = response.headers.get('content-type', '') if 'application/json' in content_type: # JSON response with audio URL data = response.json() print(f"Audio URL: {data['audioUrl']}") # Download the audio file audio_response = requests.get(data['audioUrl']) with open('output.wav', 'wb') as f: f.write(audio_response.content) else: # Binary audio data (OpenAI models) with open('output.mp3', 'wb') as f: f.write(response.content) print("Audio saved successfully!") return response else: raise Exception(f"Error: {response.status_code} - {response.text}") # Example usage try: response = text_to_speech( "Hello! This is a test of the NanoGPT text-to-speech API.", model="Kokoro-82m", voice="af_bella", speed=1.2 ) print("TTS generation completed!") except Exception as e: print(f"Error: {e}") ``` ## Async Status & Polling Some TTS models (e.g., Elevenlabs family) run asynchronously. If `POST /api/tts` returns HTTP 202 with `status: "pending"`, poll `GET /api/tts/status?runId=...&model=...` until you receive `status: "completed"` with an `audioUrl`. See endpoint details in `GET /api/tts/status`. ```javascript JavaScript theme={null} async function submitThenPollTTS({ text, model = 'Elevenlabs-Turbo-V2.5', voice = 'Rachel' }) { const submit = await fetch('https://nano-gpt.com/api/tts', { method: 'POST', headers: { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ text, model, voice }) }); if (submit.status !== 202) { if (!submit.ok) throw new Error('TTS request failed'); const ct = submit.headers.get('content-type') || ''; if (ct.includes('application/json')) return (await submit.json()).audioUrl; const blob = await submit.blob(); return URL.createObjectURL(blob); } const ticket = await submit.json(); const maxAttempts = 60, delayMs = 3000; for (let i = 0; i < maxAttempts; i++) { const qs = new URLSearchParams({ runId: ticket.runId, model: ticket.model }); const res = await fetch(`https://nano-gpt.com/api/tts/status?${qs}`, { headers: { 'x-api-key': 'YOUR_API_KEY' } }); const data = await res.json(); if (data.status === 'completed' && data.audioUrl) return data.audioUrl; if (data.status === 'error') throw new Error(data.error || 'TTS generation failed'); await new Promise(r => setTimeout(r, delayMs)); } throw new Error('Polling timeout'); } ``` ```bash cURL theme={null} # Example status poll (after receiving a pending ticket) curl "https://nano-gpt.com/api/tts/status?runId=RUN_ID&model=Elevenlabs-Turbo-V2.5" \ -H "x-api-key: YOUR_API_KEY" ``` ## Model-Specific Examples ### Kokoro-82m - Multilingual Voices Kokoro supports 44 voices across 13 language groups: ```python theme={null} # Available voice categories and examples KOKORO_VOICES = { "american_female": ["af_alloy", "af_aoede", "af_bella", "af_jessica", "af_nova"], "american_male": ["am_adam", "am_echo", "am_eric", "am_liam", "am_onyx"], "british_female": ["bf_alice", "bf_emma", "bf_isabella", "bf_lily"], "british_male": ["bm_daniel", "bm_fable", "bm_george", "bm_lewis"], "japanese_female": ["jf_alpha", "jf_gongitsune", "jf_nezumi", "jf_tebukuro"], "mandarin_female": ["zf_xiaobei", "zf_xiaoni", "zf_xiaoxiao", "zf_xiaoyi"], "french_female": ["ff_siwis"], "italian_male": ["im_nicola"], "hindi_female": ["hf_alpha", "hf_beta"] } def generate_multilingual_samples(): """Generate speech samples in different languages""" samples = [ {"text": "Hello, welcome to our service!", "voice": "af_bella", "lang": "English"}, {"text": "Bonjour, bienvenue dans notre service!", "voice": "ff_siwis", "lang": "French"}, {"text": "こんにちは、私たちのサービスへようこそ!", "voice": "jf_alpha", "lang": "Japanese"}, {"text": "你好,欢迎使用我们的服务!", "voice": "zf_xiaoxiao", "lang": "Chinese"}, {"text": "Ciao, benvenuto nel nostro servizio!", "voice": "im_nicola", "lang": "Italian"} ] for i, sample in enumerate(samples): try: response = text_to_speech( text=sample["text"], model="Kokoro-82m", voice=sample["voice"], speed=1.0 ) # Save with descriptive filename filename = f"sample_{i+1}_{sample['lang'].lower()}.wav" print(f"Generated {sample['lang']} sample: {filename}") except Exception as e: print(f"Error generating {sample['lang']} sample: {e}") generate_multilingual_samples() ``` ### Elevenlabs-Turbo-V2.5 - Premium Quality with Controls Elevenlabs offers advanced voice control options: ```python theme={null} def generate_with_voice_controls(text, voice="Rachel", **controls): """ Generate speech with advanced voice controls """ return text_to_speech( text=text, model="Elevenlabs-Turbo-V2.5", voice=voice, speed=controls.get("speed", 1.0), stability=controls.get("stability", 0.5), similarity_boost=controls.get("similarity_boost", 0.75), style=controls.get("style", 0) ) # Different voice styles examples = [ { "text": "This is a very stable and consistent voice.", "controls": {"stability": 0.9, "similarity_boost": 0.8, "style": 0} }, { "text": "This is an expressive and dynamic voice!", "controls": {"stability": 0.3, "similarity_boost": 0.7, "style": 0.8} }, { "text": "This is a balanced, natural sounding voice.", "controls": {"stability": 0.5, "similarity_boost": 0.75, "style": 0.3} } ] for i, example in enumerate(examples): try: response = generate_with_voice_controls( text=example["text"], voice="Rachel", **example["controls"] ) print(f"Generated style example {i+1}") except Exception as e: print(f"Error generating example {i+1}: {e}") # Available Elevenlabs voices ELEVENLABS_VOICES = [ "Adam", "Alice", "Antoni", "Aria", "Arnold", "Bella", "Bill", "Brian", "Callum", "Charlie", "Charlotte", "Chris", "Daniel", "Domi", "Dorothy", "Drew", "Elli", "Emily", "Eric", "Ethan", "Fin", "Freya", "George", "Gigi", "Giovanni", "Grace", "James", "Jeremy", "Jessica", "Joseph", "Josh", "Laura", "Liam", "Lily", "Matilda", "Matthew", "Michael", "Nicole", "Rachel", "River", "Roger", "Ryan", "Sam", "Sarah", "Thomas", "Will" ] ``` ### OpenAI Models - Multiple Formats and Instructions OpenAI models support various audio formats and voice instructions: ```python theme={null} def generate_openai_tts(text, model="tts-1", **options): """ Generate speech using OpenAI models with format options """ return text_to_speech( text=text, model=model, voice=options.get("voice", "nova"), speed=options.get("speed", 1.0), response_format=options.get("response_format", "mp3"), instructions=options.get("instructions") ) # Different format examples formats_demo = [ {"format": "mp3", "desc": "Compressed, good for web"}, {"format": "wav", "desc": "Uncompressed, high quality"}, {"format": "opus", "desc": "Efficient streaming codec"}, {"format": "flac", "desc": "Lossless compression"} ] text = "This audio demonstrates different format options." for fmt in formats_demo: try: response = generate_openai_tts( text=text, model="tts-1-hd", voice="nova", response_format=fmt["format"] ) print(f"Generated {fmt['format'].upper()} format: {fmt['desc']}") except Exception as e: print(f"Error with {fmt['format']}: {e}") # Voice instructions example (tts-1-hd and gpt-4o-mini-tts) instruction_examples = [ { "text": "Welcome to our customer service line.", "instructions": "Speak warmly and professionally, like a friendly customer service representative" }, { "text": "Breaking news: Scientists make major discovery!", "instructions": "Speak with the excitement and urgency of a news reporter" }, { "text": "Once upon a time, in a faraway land...", "instructions": "Tell this like a bedtime story, gentle and soothing" } ] for example in instruction_examples: try: response = generate_openai_tts( text=example["text"], model="gpt-4o-mini-tts", # Supports instructions voice="alloy", instructions=example["instructions"] ) print(f"Generated with instructions: {example['instructions'][:50]}...") except Exception as e: print(f"Error: {e}") # Available OpenAI voices OPENAI_VOICES = ["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"] ``` ## Complete TTS Class Implementation Here's a comprehensive TTS class with all model support: ```python theme={null} import requests import json from pathlib import Path class NanoGPTTTS: def __init__(self, api_key): self.api_key = api_key self.base_url = "https://nano-gpt.com/api" # Model capabilities self.model_info = { "Kokoro-82m": { "cost_per_1k": 0.001, "max_chars": 10000, "supports_speed": True, "output_format": "wav", "binary_response": False }, "Elevenlabs-Turbo-V2.5": { "cost_per_1k": 0.06, "max_chars": 10000, "supports_speed": True, "output_format": "mp3", "binary_response": False, "supports_voice_controls": True }, "tts-1": { "cost_per_1k": 0.015, "max_chars": 4096, "supports_speed": True, "binary_response": True, "supports_formats": True }, "tts-1-hd": { "cost_per_1k": 0.030, "max_chars": 4096, "supports_speed": True, "binary_response": True, "supports_formats": True, "supports_instructions": True }, "gpt-4o-mini-tts": { "cost_per_1k": 0.0006, "max_chars": 4096, "supports_speed": False, # Speed ignored "binary_response": True, "supports_formats": True, "supports_instructions": True } } def synthesize(self, text, model="Kokoro-82m", output_file=None, **kwargs): """ Main synthesis method with automatic parameter handling """ # Validate inputs self._validate_request(text, model, **kwargs) # Prepare request headers = { "x-api-key": self.api_key, "Content-Type": "application/json" } payload = self._build_payload(text, model, **kwargs) # Make request response = requests.post( f"{self.base_url}/tts", headers=headers, json=payload ) if response.status_code == 200: return self._handle_response(response, model, output_file) else: self._handle_error(response) def _validate_request(self, text, model, **kwargs): """Validate request parameters""" if not text.strip(): raise ValueError("Text cannot be empty") if model not in self.model_info: raise ValueError(f"Unsupported model: {model}") model_config = self.model_info[model] if len(text) > model_config["max_chars"]: raise ValueError(f"Text too long for {model}. Max: {model_config['max_chars']} chars") # Validate speed parameter if kwargs.get("speed") and not model_config.get("supports_speed", True): print(f"Warning: Speed parameter ignored for {model}") def _build_payload(self, text, model, **kwargs): """Build request payload based on model capabilities""" payload = { "text": text, "model": model } model_config = self.model_info[model] # Add voice if specified if kwargs.get("voice"): payload["voice"] = kwargs["voice"] # Add speed if supported if kwargs.get("speed") and model_config.get("supports_speed"): payload["speed"] = kwargs["speed"] # Add OpenAI-specific parameters if model.startswith(("tts-", "gpt-")): if kwargs.get("response_format"): payload["response_format"] = kwargs["response_format"] if kwargs.get("instructions") and model_config.get("supports_instructions"): payload["instructions"] = kwargs["instructions"] # Add Elevenlabs-specific parameters elif model == "Elevenlabs-Turbo-V2.5": for param in ["stability", "similarity_boost", "style"]: if kwargs.get(param) is not None: payload[param] = kwargs[param] return payload def _handle_response(self, response, model, output_file): """Handle different response types""" model_config = self.model_info[model] if model_config.get("binary_response"): # Binary audio data (OpenAI models) audio_data = response.content if output_file: with open(output_file, 'wb') as f: f.write(audio_data) return {"audio_file": output_file, "size": len(audio_data)} else: return {"audio_data": audio_data, "size": len(audio_data)} else: # JSON response with URL data = response.json() if output_file: # Download and save audio audio_response = requests.get(data['audioUrl']) with open(output_file, 'wb') as f: f.write(audio_response.content) data["local_file"] = output_file return data def _handle_error(self, response): """Handle API errors""" try: error_data = response.json() error_msg = error_data.get('error', 'Unknown error') except: error_msg = f"HTTP {response.status_code}" if response.status_code == 400: raise ValueError(f"Bad request: {error_msg}") elif response.status_code == 401: raise ValueError("Unauthorized: Check your API key") elif response.status_code == 402: raise ValueError("Insufficient balance") elif response.status_code == 413: raise ValueError("Text too long") else: raise Exception(f"API Error: {error_msg}") def get_model_info(self, model=None): """Get information about available models""" if model: return self.model_info.get(model, {}) return self.model_info def batch_synthesize(self, texts, model="Kokoro-82m", **kwargs): """Synthesize multiple texts""" results = [] for i, text in enumerate(texts): try: output_file = f"batch_output_{i+1}.wav" if kwargs.get("save_files") else None result = self.synthesize(text, model, output_file, **kwargs) results.append({"index": i, "success": True, "result": result}) except Exception as e: results.append({"index": i, "success": False, "error": str(e)}) return results # Usage examples tts = NanoGPTTTS("YOUR_API_KEY") # Simple usage result = tts.synthesize( "Hello world!", model="Kokoro-82m", voice="af_bella", output_file="hello.wav" ) # Advanced Elevenlabs usage result = tts.synthesize( "This is an expressive voice demonstration!", model="Elevenlabs-Turbo-V2.5", voice="Rachel", stability=0.3, similarity_boost=0.8, style=0.7, speed=1.1, output_file="expressive.mp3" ) # OpenAI with instructions result = tts.synthesize( "Welcome to our premium service.", model="tts-1-hd", voice="nova", instructions="Speak like a luxury brand representative", response_format="flac", output_file="premium.flac" ) # Batch processing texts = [ "First audio clip.", "Second audio clip.", "Third audio clip." ] batch_results = tts.batch_synthesize( texts, model="gpt-4o-mini-tts", voice="alloy", save_files=True ) for result in batch_results: if result["success"]: print(f"Generated file {result['index']}: {result['result'].get('local_file')}") else: print(f"Failed file {result['index']}: {result['error']}") ``` ## Best Practices and Tips ### Character Limits and Costs ```python theme={null} def optimize_text_for_model(text, model="Kokoro-82m"): """ Optimize text based on model limitations """ tts = NanoGPTTTS("YOUR_API_KEY") model_info = tts.get_model_info(model) max_chars = model_info.get("max_chars", 10000) if len(text) <= max_chars: return [text] # Split long text into chunks sentences = text.split('. ') chunks = [] current_chunk = "" for sentence in sentences: if len(current_chunk + sentence + '. ') <= max_chars: current_chunk += sentence + '. ' else: if current_chunk: chunks.append(current_chunk.strip()) current_chunk = sentence + '. ' if current_chunk: chunks.append(current_chunk.strip()) return chunks # Example usage long_text = "Your very long text here..." * 100 # Simulate long text chunks = optimize_text_for_model(long_text, "tts-1") print(f"Split into {len(chunks)} chunks") for i, chunk in enumerate(chunks): result = tts.synthesize( chunk, model="tts-1", voice="nova", output_file=f"chunk_{i+1}.mp3" ) print(f"Generated chunk {i+1}") ``` ### Voice Selection Guide ```python theme={null} def suggest_voice(content_type, model="Kokoro-82m"): """ Suggest appropriate voices based on content type """ suggestions = { "Kokoro-82m": { "professional": ["af_bella", "am_adam", "bf_alice", "bm_daniel"], "friendly": ["af_nova", "af_aoede", "am_eric", "bf_emma"], "authoritative": ["am_onyx", "bm_george", "am_liam"], "storytelling": ["af_jessica", "bf_lily", "am_echo"], "multilingual": { "japanese": ["jf_alpha", "jf_gongitsune", "jm_kumo"], "chinese": ["zf_xiaoxiao", "zf_xiaoyi", "zm_yunxi"], "french": ["ff_siwis"], "italian": ["im_nicola"] } }, "Elevenlabs-Turbo-V2.5": { "professional": ["Rachel", "Sarah", "Matthew", "Daniel"], "friendly": ["Bella", "Grace", "Josh", "Ryan"], "authoritative": ["Adam", "James", "Michael"], "storytelling": ["Alice", "Emily", "Jeremy"] }, "OpenAI": { "professional": ["nova", "echo", "onyx"], "friendly": ["alloy", "shimmer", "coral"], "authoritative": ["fable", "ash"], "storytelling": ["ballad", "sage", "verse"] } } model_key = "OpenAI" if model.startswith(("tts-", "gpt-")) else model return suggestions.get(model_key, {}).get(content_type, []) # Example usage print("Professional voices for Kokoro:", suggest_voice("professional", "Kokoro-82m")) print("Storytelling voices for OpenAI:", suggest_voice("storytelling", "tts-1")) ``` ## Error Handling and Troubleshooting ```python theme={null} import time from requests.exceptions import RequestException, Timeout def robust_tts_request(text, model="Kokoro-82m", max_retries=3, **kwargs): """ TTS request with retry logic and comprehensive error handling """ tts = NanoGPTTTS("YOUR_API_KEY") for attempt in range(max_retries): try: return tts.synthesize(text, model, **kwargs) except ValueError as e: # Parameter errors - don't retry print(f"Parameter error: {e}") break except RequestException as e: if attempt < max_retries - 1: wait_time = 2 ** attempt # Exponential backoff print(f"Network error (attempt {attempt + 1}): {e}") print(f"Retrying in {wait_time} seconds...") time.sleep(wait_time) else: print(f"Max retries exceeded: {e}") except Exception as e: print(f"Unexpected error: {e}") break return None # Usage with error handling result = robust_tts_request( "This request will retry on network errors.", model="Elevenlabs-Turbo-V2.5", voice="Rachel", output_file="robust_output.mp3" ) if result: print("TTS generation successful!") else: print("TTS generation failed after all retries.") ``` ## Pricing Summary | Model | Cost per 1k chars | Max Length | Streaming (`/v1/audio/speech`) | Special Features | | --------------------- | ----------------- | ---------- | ------------------------------ | -------------------------------- | | Kokoro-82m | \$0.001 | 10,000 | No (`stream` ignored) | 44 multilingual voices | | Elevenlabs-Turbo-V2.5 | \$0.06 | 10,000 | Yes | Voice controls, 46 voices | | Elevenlabs-V3 | Varies | Varies | Yes | Expressive ElevenLabs generation | | tts-1 | \$0.015 | 4,096 | Yes | Multiple formats | | tts-1-hd | \$0.030 | 4,096 | Yes | HD quality, voice instructions | | gpt-4o-mini-tts | \$0.0006 | 4,096 | Yes | Ultra-low cost | # Video Generation Source: https://docs.nano-gpt.com/api-reference/video-generation Complete guide to video generation APIs ## Overview The NanoGPT API provides advanced video generation capabilities using state-of-the-art models. This guide covers how to use our video generation endpoints. For authenticated API-key requests, you can opt in to a paid input safety preflight by sending the `moderation` header. See [Inline Moderation](/api-reference/miscellaneous/inline-moderation) for supported video routes, inspectable image inputs, billing behavior, and error codes. ## API Authentication Supported authentication methods: 1. **API Key header**: `x-api-key: ` 2. **Bearer token**: `Authorization: Bearer ` 3. **Cookie session**: for web clients (automatic) ### Header Examples ```bash theme={null} # Using x-api-key header curl -H "x-api-key: YOUR_API_KEY" # Using Bearer token curl -H "Authorization: Bearer YOUR_API_KEY" ``` ## Making a Video Generation Request ### Endpoint ``` POST /api/generate-video ``` ### Request Headers ```http theme={null} Content-Type: application/json x-api-key: YOUR_API_KEY # Optional, for API key auth ``` ### Request Body #### Basic Text-to-Video Request ```json theme={null} { "model": "veo2-video", "prompt": "A majestic eagle soaring through mountain peaks at sunset", "duration": "5s", "aspect_ratio": "16:9" } ``` #### Image-to-Video Request Image-conditioned models accept either `imageDataUrl` (base64) or `imageUrl` (a public HTTPS link). The platform always uses the explicit field you send before falling back to any library attachments. > Uploads sent via the API must be 4 MB or smaller. For larger assets, host them externally and provide an `imageUrl`. ##### Base64 input ```json theme={null} { "model": "kling-v21-standard", "prompt": "Make the person in the image wave hello", "imageDataUrl": "data:image/jpeg;base64,/9j/4AAQ...", "duration": "5", "aspect_ratio": "16:9" } ``` ##### Public URL input ```json theme={null} { "model": "kling-v21-standard", "prompt": "Make the person in the image wave hello", "imageUrl": "https://assets.example.com/reference/wave-hello.jpg", "duration": "5", "aspect_ratio": "16:9" } ``` ## Additional Request Parameters These parameters are available across models. Only send the fields your chosen model supports. ### Core Parameters | Parameter | Type | Description | | ------------------- | ------ | ---------------------------------------------------------------------------------------- | | `conversationUUID` | string | Link a generation to a conversation | | `resolution` | string | Output resolution (`480p`, `720p`, `1080p`) | | `imageAttachmentId` | string | Reference to a library-stored image | | `videoUrl` | string | Public HTTPS link to a source video (extend/edit/upscale) | | `videoDataUrl` | string | Base64-encoded data URL for a source video (max 4 MB) | | `videoAttachmentId` | string | Reference to a library-stored video | | `video` | string | Alternate video field accepted by select models (for example, `wan-wavespeed-25-extend`) | | `referenceImages` | array | Multiple reference images for reference-to-video mode | | `referenceVideos` | array | Multiple reference videos | > Prefer `videoUrl` (camelCase) for source videos. Only send `video` if the model explicitly requires it. ### Audio Parameters (lipsync/avatar models) | Parameter | Type | Description | | --------------- | ------ | ------------------------------------- | | `audioDataUrl` | string | Base64-encoded audio | | `audioDuration` | number | Duration of provided audio in seconds | | `voiceId` | string | Voice selection for lipsync models | ### Model-Specific Parameters | Parameter | Type | Description | | ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `pro` / `pro_mode` | boolean | Enable pro/higher-quality mode | | `generateAudio` | boolean | Generate audio with video (Veo 3) | | `mode` | string | `text-to-video`, `image-to-video`, `reference-to-video`, `video-edit` | | `orientation` | string | Portrait or landscape (Sora 2) | | `size` | string | Output dimensions (Pixverse, Wan models) | | `animation` | boolean | Enable animation (Longstories) | | `language` | string | Output language (Longstories) | | `characters` | array | Character definitions (Longstories) | | `style` | string | Style preset | | `seed` | number or string | Optional seed forwarded on model/provider routes that support it. This may improve reproducibility but does not guarantee identical output. | ### Model-Specific Parameters #### Veo Models ```json theme={null} { "model": "veo2-video", "prompt": "Your prompt", "duration": "5s", // 5s-30s for Veo2, fixed 8s for Veo3 "aspect_ratio": "16:9" // 16:9, 9:16, 1:1, 4:3, 3:4 } ``` #### Kling Models ```json theme={null} { "model": "kling-video-v2", "prompt": "Your prompt", "duration": "5", // "5" or "10" "aspect_ratio": "16:9", "negative_prompt": "blur, distortion", // Optional "cfg_scale": 0.5 // 0-1, default 0.5 } ``` #### Hunyuan Models ```json theme={null} { "model": "hunyuan-video", "prompt": "Your prompt", "pro_mode": false, // true for higher quality (2x cost) "aspect_ratio": "16:9", "resolution": "720p", // 480p, 720p, 1080p "num_frames": 129, // 65, 97, 129 "num_inference_steps": 20, // 10-50 "showExplicitContent": false // Safety filter } ``` #### Wan Image-to-Video > Accepts base64 via `imageDataUrl` or a public URL via `imageUrl`. ```json theme={null} { "model": "wan-video-image-to-video", "prompt": "Your prompt", "imageDataUrl": "data:image/...", "num_frames": 81, // 81-100 "frames_per_second": 16, // 5-24 "resolution": "720p", // 480p or 720p "num_inference_steps": 30, // 1-40 "negative_prompt": "blur, distortion", "seed": 42 // Optional } ``` #### Seedance Models > Accepts base64 via `imageDataUrl` or a public URL via `imageUrl`. Ensure URLs are directly fetchable. ```json theme={null} { "model": "seedance-video", "prompt": "Your prompt", "resolution": "1080p", // 480p or 1080p (standard), 480p or 720p (lite) "duration": "5", // "5" or "10" "aspect_ratio": "16:9", // T2V only "camera_fixed": false, // Static camera "seed": 42 // Optional } ``` ## Supported Models ### Text-to-Video Models | Model | Duration | Notes | | ---------------------------- | -------- | ------------------------------ | | `veo2-video` | 5-8s | Also supports image-to-video | | `veo3-video` | 5-8s | Audio generation supported | | `veo3-1-video` | Variable | T2V and I2V modes | | `veo3-fast-video` | Variable | Fast generation | | `sora-2` | Variable | Pro mode, multiple resolutions | | `kling-video` | 5-10s | Basic text-to-video | | `kling-video-v2` | 5-10s | Enhanced quality | | `kling-video-o1` | 5-10s | Multi-mode support | | `kling-video-o1-standard` | 5-10s | Standard variant | | `kling-v25-turbo-pro` | 5-10s | Turbo pro | | `kling-v25-turbo-std` | 5-10s | Turbo standard | | `kling-v26-pro` | 5-10s | Latest pro | | `minimax-video` | 6s | Fixed duration | | `minimax-hailuo-02` | Variable | Per-second pricing | | `minimax-hailuo-02-pro` | Variable | Premium variant | | `minimax-hailuo-23-standard` | Variable | Hailuo 2.3 | | `minimax-hailuo-23-pro` | Variable | Hailuo 2.3 pro | | `hunyuan-video` | 5s | Pro mode available | | `hunyuan-video-15` | Variable | Resolution-based | | `wan-video-22` | 5s | 14B full model | | `wan-video-22-5b` | 5s | 5B lite model | | `wan-video-22-turbo` | 5s | Simplified | | `wan-wavespeed-25` | Variable | Wan 2.5 | | `wan-wavespeed-26` | Variable | Wan 2.6 | | `wan-wavespeed-22-plus` | Variable | Plus variant | | `seedance-video` | Variable | Resolution-duration pricing | | `seedance-lite-video` | Variable | Lite version | | `pixverse-v45` | Variable | V4.5 | | `pixverse-v5` | Variable | V5 | | `pixverse-v55` | Variable | V5.5 with effects | | `pixverse-v55-effects` | Variable | Effects variant | | `lightricks-ltx-2-fast` | Variable | Fast generation | | `lightricks-ltx-2-pro` | Variable | Pro quality | | `vidu-video` | Variable | Vidu Q1 | | `runwayml-gen4-aleph` | Variable | Runway Gen4 | | `veed-fabric-1.0` | Variable | Veed fabric | | `midjourney-video` | Variable | Returns 4 videos | ### Image-to-Video Only Models | Model | Notes | | ------------------------------ | ------------------------------- | | `kling-v21-standard` | Standard quality | | `kling-v21-pro` | Pro quality | | `kling-v21-master` | Master quality, requires prompt | | `hunyuan-video-image-to-video` | Image input required | | `wan-video-image-to-video` | Image input required | ### Avatar/Lipsync Models | Model | Input | Notes | | --------------------------------- | ------------- | ---------------------- | | `kling-v2-avatar-standard` | Audio + Image | Standard avatar | | `kling-v2-avatar-pro` | Audio + Image | Pro avatar | | `kling-lipsync-t2v` | Text | Text-to-video lipsync | | `kling-lipsync-a2v` | Audio | Audio-to-video lipsync | | `latentsync` | Audio + Video | Audio-video sync | | `bytedance-avatar-omni-human-1.5` | Audio + Image | Bytedance avatar | | `bytedance-waver-1.0` | Audio + Image | Waver model | ### Utility Models | Model | Purpose | | ----------------------------- | ------------------------ | | `video-upscaler` | Upscale video resolution | | `bytedance-seedance-upscaler` | Alternative upscaler | | `seedvr2-video-upscaler` | SeedVR upscaler | | `wan-wavespeed-video-edit` | Video editing | | `wan-wavespeed-22-animate` | Video animation | | `wan-wavespeed-s2v` | Speech-to-video | | `magicapi-video-face-swap` | Face swap | ### Extension Models Extend models run through `POST /api/generate-video` with `prompt` plus a source video input (`videoUrl`, `videoDataUrl`, or `videoAttachmentId`). The task-based `/api/generate-video/extend` endpoint is only for Midjourney extensions. Max source video length: 120 seconds. | Model | Purpose | | ------------------------------------ | ---------------------- | | `wan-wavespeed-25-extend` | Extend Wan videos | | `wan-wavespeed-22-spicy-extend` | Spicy extension | | `veo3-1-extend` | Extend VEO videos | | `veo3-1-fast-extend` | Fast VEO extension | | `bytedance-seedance-v1.5-pro-extend` | Extend Seedance videos | ### Longstories Models (Scripted Video) | Model | Notes | | ----------------------- | ------------------------------------- | | `longstories-movie` | Movie generation with voice narration | | `longstories-pixel-art` | Pixel art style | ## Response Format ### Initial Response (202 Accepted) ```json theme={null} { "runId": "vid_m1abc123def456", "id": "vid_m1abc123def456", "status": "pending", "model": "veo2-video", "cost": 0.35, "paymentSource": "XNO", "remainingBalance": 12.5, "prechargeLabel": "string" } ``` ### Response Fields * `runId`: NanoGPT job ID used for polling (format `vid_...`) * `id`: Alias for `runId` (same value) * `status`: Always "pending" for initial response * `model`: The model used for generation * `cost`: Estimated/pre-charged cost * `paymentSource`: "USD" or "XNO" * `remainingBalance`: Account balance after deduction * `prechargeLabel`: Billing label for the precharge ## Cost Information Both the initial generation response and the status response include `cost`: * Initial response: estimated/pre-charged cost * Status response: final cost (when `status` is `COMPLETED`) Pricing structures vary by model: * **Fixed**: flat rate per generation * **Per-Second**: rate x duration * **Resolution-Based**: rates per resolution tier * **Duration-Based**: step pricing (5s vs 10s) * **Mode-Based**: different rates for T2V vs I2V ## Polling for Status After receiving a `runId`, poll the status endpoint until completion. ### Status Endpoint ``` GET /api/video/status?requestId={runId} ``` You can send `requestId` or `runId`; no model parameter is required. ### Polling Example ```javascript theme={null} async function pollVideoStatus(runId) { const maxAttempts = 120; // ~10 minutes total const delayMs = 5000; // 5 seconds (max ~10 minutes) for (let i = 0; i < maxAttempts; i++) { const response = await fetch( `/api/video/status?requestId=${runId}` ); const result = await response.json(); if (result.data.status === 'COMPLETED') { return result.data.output.video.url; } else if (result.data.status === 'FAILED') { throw new Error(result.data.error || 'Video generation failed'); } // Wait before next poll await new Promise((resolve) => setTimeout(resolve, delayMs)); } throw new Error('Video generation timed out'); } ``` ### Status Response States #### In Progress ```json theme={null} { "requestId": "vid_m1abc123def456", "model": "veo2-video", "data": { "status": "IN_PROGRESS", "requestId": "vid_m1abc123def456", "details": "Video is being generated" } } ``` #### Completed ```json theme={null} { "requestId": "vid_m1abc123def456", "model": "veo2-video", "data": { "status": "COMPLETED", "requestId": "vid_m1abc123def456", "output": { "video": { "url": "https://storage.example.com/video.mp4" } }, "cost": 0.35 } } ``` #### Failed ```json theme={null} { "requestId": "vid_m1abc123def456", "model": "veo2-video", "data": { "status": "FAILED", "requestId": "vid_m1abc123def456", "error": "Content policy violation", "isNSFWError": true, "userFriendlyError": "Content flagged as inappropriate. Please modify your prompt and try again." } } ``` ### Status Values * `IN_QUEUE`: Request is queued * `IN_PROGRESS`: Video is being generated * `COMPLETED`: Video ready for download * `FAILED`: Generation failed * `CANCELED`: Request was canceled ## Additional Endpoints ### GET `/api/generate-video/recover` Recover recent video generation runs for a user. **Query Parameters** | Parameter | Type | Required | Description | | ------------------ | ------ | -------- | -------------------------------- | | `model` | string | No | Filter by model | | `limit` | number | No | Max results (default 10, max 50) | | `conversationUUID` | string | No | Filter by conversation | **Rate Limit**: 20 requests/minute ### POST `/api/generate-video/extend` Extend a Midjourney video using a task-based flow. **Rate Limit**: 20 requests/minute **Required Fields**: `runId` (preferred) or `taskId` (legacy alias), `index` (0-3) **Notes**: * This endpoint does **not** accept `video`, `videoUrl`, `videoDataUrl`, or `videoAttachmentId`. * Session ownership is enforced; requests for jobs you do not own return `403`. * Use `POST /api/generate-video` with an extend model for source-video extension. ### GET `/api/generate-video/content` Proxy content retrieval for Sora 2 videos. **Query Parameters** | Parameter | Type | Required | Description | | --------- | ------ | -------- | -------------------------------------- | | `runId` | string | Yes | The run ID | | `model` | string | Yes | Must be `sora-2` | | `variant` | string | No | `video`, `thumbnail`, or `spritesheet` | ## Complete Examples The submit + poll flow works the same regardless of how you supply the image: image-conditioned models accept either `imageDataUrl` (base64) or a public `imageUrl`, and the platform prefers whichever field you send before checking library attachments. ### Example 1: Text-to-Video with cURL ```bash theme={null} # 1) Submit RUN_ID=$(curl -s -X POST https://nano-gpt.com/api/generate-video \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "kling-video-v2", "prompt": "A cat playing piano in a jazz club", "duration": "5" }' | jq -r '.runId') echo "Run ID: $RUN_ID" # 2) Poll status (max ~10 minutes) for i in {1..120}; do RESP=$(curl -s "https://nano-gpt.com/api/video/status?requestId=$RUN_ID" \ -H "x-api-key: YOUR_API_KEY") STATUS=$(echo "$RESP" | jq -r '.data.status // empty') echo "Attempt $i: status=$STATUS" if [ "$STATUS" = "COMPLETED" ]; then echo "Completed response:" echo "$RESP" | jq . VIDEO_URL=$(echo "$RESP" | jq -r '.data.output.video.url') echo "Video URL: $VIDEO_URL" break fi sleep 5 done # Download when ready # curl -L "$VIDEO_URL" -o output.mp4 ``` ### Example 2: Image-to-Video with cURL #### Base64 input ```bash theme={null} RUN_ID=$(curl -s -X POST https://nano-gpt.com/api/generate-video \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "kling-v21-pro", "prompt": "The subject walks toward the camera", "imageDataUrl": "data:image/jpeg;base64,/9j/4AAQ...", "duration": "5", "aspect_ratio": "16:9" }' | jq -r '.runId') ``` #### Public URL input ```bash theme={null} RUN_ID=$(curl -s -X POST https://nano-gpt.com/api/generate-video \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "kling-v21-pro", "prompt": "The subject walks toward the camera", "imageUrl": "https://images.unsplash.com/photo-1504196606672-aef5c9cefc92?w=1024", "duration": "5", "aspect_ratio": "16:9" }' | jq -r '.runId') ``` Use the same polling loop from Example 1 to monitor either request. ### Example 3: Image-to-Video with JavaScript ```javascript theme={null} // 1. Convert image to base64 async function imageToBase64(imageFile) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(imageFile); }); } // 2. Submit video generation async function generateVideo(imageFile) { const imageDataUrl = await imageToBase64(imageFile); const response = await fetch('/api/generate-video', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify({ model: 'kling-v21-pro', prompt: 'Add gentle camera movement to this scene', imageDataUrl: imageDataUrl, duration: '5', aspect_ratio: '16:9' }) }); const result = await response.json(); console.log('Video generation started:', result.runId); // 3. Poll for completion const videoUrl = await pollVideoStatus(result.runId); console.log('Video ready:', videoUrl); return videoUrl; } ``` #### Using a public image URL directly ```javascript theme={null} async function generateVideoFromUrl(imageUrl) { const response = await fetch('/api/generate-video', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify({ model: 'kling-v21-pro', prompt: 'Add gentle camera movement to this scene', imageUrl, duration: '5', aspect_ratio: '16:9' }) }); const result = await response.json(); const videoUrl = await pollVideoStatus(result.runId); return videoUrl; } ``` ### Example 4: Image-to-Video with Python ```python theme={null} import base64 import json import requests API_URL = "https://nano-gpt.com/api/generate-video" API_KEY = "YOUR_API_KEY" def submit_image_to_video(image_path: str) -> str: with open(image_path, "rb") as image_file: encoded = base64.b64encode(image_file.read()).decode("utf-8") payload = { "model": "kling-v21-pro", "prompt": "Animate this scene with a slow dolly zoom", "imageDataUrl": f"data:image/jpeg;base64,{encoded}", "duration": "5", "aspect_ratio": "16:9", } response = requests.post( API_URL, headers={ "x-api-key": API_KEY, "Content-Type": "application/json", }, data=json.dumps(payload), timeout=60, ) response.raise_for_status() return response.json()["runId"] ``` ```python theme={null} def submit_image_url(image_url: str) -> str: payload = { "model": "kling-v21-pro", "prompt": "Animate this scene with a slow dolly zoom", "imageUrl": image_url, "duration": "5", "aspect_ratio": "16:9", } response = requests.post( API_URL, headers={ "x-api-key": API_KEY, "Content-Type": "application/json", }, data=json.dumps(payload), timeout=60, ) response.raise_for_status() return response.json()["runId"] ``` Reuse the polling helper from the JavaScript example (or your own status loop) to watch these run IDs until completion. ### Example 5: Batch Processing ```javascript theme={null} async function generateMultipleVideos(prompts) { // Submit all requests const requests = await Promise.all( prompts.map(async (prompt) => { const response = await fetch('/api/generate-video', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify({ model: 'seedance-lite-video', prompt: prompt, duration: '5', resolution: '720p' }) }); return response.json(); }) ); // Poll all statuses concurrently const videos = await Promise.all( requests.map(({ runId, model }) => pollVideoStatus(runId) ) ); return videos; } ``` ## Error Handling ### Error Response Format ```json theme={null} { "error": { "message": "User-friendly error message", "type": "ERROR_TYPE" }, "refundMessage": "Refund notification if applicable" } ``` **Error Types**: `CONTENT_POLICY_VIOLATION`, `PRO_REQUIRED`, `INSUFFICIENT_BALANCE`, `RATE_LIMITED` ### Error Handling Best Practices ```javascript theme={null} async function generateVideoWithErrorHandling(params) { try { // Submit request const response = await fetch('/api/generate-video', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify(params) }); if (!response.ok) { const error = await response.json(); // Handle specific error types if (response.status === 429 || error.error?.type === 'RATE_LIMITED') { console.error('Rate limited, retry after delay'); // Implement exponential backoff } else if (response.status === 402 || error.error?.type === 'INSUFFICIENT_BALANCE') { console.error('Insufficient balance'); // Prompt user to add credits } else if (error.error?.type === 'CONTENT_POLICY_VIOLATION') { console.error('Content policy violation'); // Show user-friendly message } throw new Error(error.error?.message || error.error); } const result = await response.json(); // Poll for status with timeout const videoUrl = await pollVideoStatus(result.runId); return videoUrl; } catch (error) { console.error('Video generation failed:', error); throw error; } } ``` ## Rate Limits | Endpoint | Limit | | --------------------------------- | --------------------------------- | | POST `/api/generate-video` | 50 requests/minute per IP | | GET `/api/video/status` | No explicit limit (cached) | | GET `/api/generate-video/status` | Deprecated (legacy compatibility) | | POST `/api/generate-video/extend` | 20 requests/minute per IP | | GET `/api/generate-video/recover` | 20 requests/minute per IP | ## Additional Notes ### Pro Mode * `sora-2`: Pro mode required for 1792x1024 resolution * `hunyuan-video`: Pro mode available * Various Kling models: Pro variants ### Audio Generation * `veo3-video`: Set `generateAudio: true` to include audio ### Reference-to-Video Supported by `kling-video-o1`, `kling-video-o1-standard`, `wan-wavespeed-26`. Use `referenceImages` with image URLs or data URLs. ### Automatic Refunds Refunds are automatically issued when: * The job returns a failure * Content policy violation (before processing) * Submission fails before acknowledgement ## Best Practices 1. **Choose the Right Model** * Use text-to-video for creative generation * Use image-to-video for animating existing content * Consider cost vs quality tradeoffs 2. **Optimize Prompts** * Be specific and descriptive * Include motion and camera directions * Avoid content policy violations 3. **Handle Async Operations** * Implement proper polling with delays * Set reasonable timeouts (5-10 minutes) * Show progress to users 4. **Error Recovery** * Implement retry logic for transient failures * Handle rate limits with exponential backoff * Provide clear error messages to users 5. **Cost Management** * Check balance before submitting * Estimate costs before generation * Use shorter durations for testing # Authentication Source: https://docs.nano-gpt.com/authentication How to authenticate with the NanoGPT API and web app. ## API Authentication (Recommended) Most integrations should use an **API key**. ### API key format New keys use the format: ```text theme={null} sk-nano- ``` Some older accounts may still have **legacy** keys (a plain UUID). Both formats are accepted. ### Send your key (headers) Use one of these headers on every request: 1. `Authorization: Bearer ` (recommended) 2. `X-API-Key: ` Example: ```bash theme={null} curl "https://nano-gpt.com/api/v1/models" \ -H "Authorization: Bearer sk-nano-YOUR_API_KEY" ``` ### Common errors * `401 Unauthorized`: Missing/invalid/revoked API key (or your key is inactive/expired, if your account has expiration enabled) * `403 Forbidden`: API key browser-origin restriction failed (`api_key_origin_not_allowed`) * `429 Too Many Requests`: Rate limit exceeded (per-second throughput, or a per-key daily cap if configured) For rate-limit details, see [Rate Limits](/api-reference/miscellaneous/rate-limits). ### Getting an API key Create and manage API keys in the NanoGPT dashboard: [https://nano-gpt.com/api](https://nano-gpt.com/api) ## CLI Authentication (Device Login) If you're building a CLI, use **device login** so users can approve access in a browser and your CLI receives an API key (`sk-nano-...`). See: [CLI Device Login](/integrations/cli-login) ## OAuth PKCE (Sign in with NanoGPT) If you're building a third-party app, local tool, coding agent, or generic OAuth client, use **OAuth PKCE** so users can approve access in NanoGPT instead of manually creating and pasting an API key. OAuth returns an app-specific NanoGPT API key (`sk-nano-...`) that your app sends as: ```http theme={null} Authorization: Bearer sk-nano-... ``` See: [OAuth PKCE](/api-reference/miscellaneous/oauth-pkce) ## Web App Sign-In (Browser) If you are using the NanoGPT web app, sign-in is handled via browser sessions. Supported sign-in methods include: * OAuth (GitHub, Google) * Email one-time code (magic link / verification code) * Email/password (or username/password, where supported) * Passkey (WebAuthn) If you are calling the API from a backend service, prefer API keys instead of relying on browser cookies. ## API Key Security Best Practices * Store keys in environment variables (for example: `NANOGPT_API_KEY`). * Never commit keys to git or ship them in client-side code. * Prefer `Authorization: Bearer ...` over putting keys in URLs. * For browser-based apps, set **Allowed browser origins** when creating the API key so the key is only accepted from your app's origin. Prefer backend calls or OAuth PKCE when possible; do not embed unrestricted API keys in client-side code. * Use separate keys per app/environment so you can revoke access without breaking everything. * Set spending and request limits (if available for your account) to cap blast radius. # Integrations Source: https://docs.nano-gpt.com/integrations Set up NanoGPT in coding agents, chat frontends, automation tools, and OpenAI-compatible clients. # Integrations NanoGPT works with OpenAI-compatible tools and a growing set of coding agents, chat frontends, automation platforms, and local clients. Use these guides to connect your NanoGPT API key to the tool you want to use. ## Popular Guides * [OpenCode](/integrations/opencode) - Configure OpenCode to use NanoGPT models from your terminal. * [SillyTavern](/integrations/sillytavern) - Connect character-based chats to NanoGPT. * [Kilo Code](/integrations/kilocode) - Add NanoGPT as an OpenAI-compatible provider. * [Fluent](/integrations/fluent) - Use NanoGPT models and MCP tools in Fluent for macOS. * [JanitorAI](/integrations/janitorai) - Route JanitorAI proxy chats through NanoGPT. * [RisuAI](/integrations/risuai) - Use NanoGPT with RisuAI chats and characters. * [OpenWebUI](/integrations/openwebui) - Add NanoGPT as an OpenAI-compatible connection. ## Coding Agents * [Claude Code](/integrations/claude-code) * [Cline](/integrations/cline) * [Codex CLI](/integrations/codex-cli) * [Cursor](/integrations/cursor) * [Gemini CLI](/integrations/gemini-cli) * [Grok CLI](/integrations/grok-cli) * [OpenCode](/integrations/opencode) * [OpenHands](/integrations/openhands) * [Roo Code](/integrations/roocode) ## Chat Frontends * [JanitorAI](/integrations/janitorai) * [LibreChat](/integrations/librechat) * [OpenClaw](/integrations/openclaw) * [OpenWebUI](/integrations/openwebui) * [Otaku](/integrations/otaku) * [RisuAI](/integrations/risuai) * [SillyTavern](/integrations/sillytavern) * [TypingMind](/integrations/typingmind) ## Automation And API Tools * [CLI Login](/integrations/cli-login) * [Droid](/integrations/droid) * [Fluent](/integrations/fluent) * [LiteLLM](/integrations/litellm) * [MCP](/integrations/mcp) * [n8n](/integrations/n8n) # Claude Code Source: https://docs.nano-gpt.com/integrations/claude-code Use Claude Code with NanoGPT and Claude + 400 models # Claude Code > Use Claude Code with NanoGPT to access Claude and 400+ AI models through a single API key. [Get started with NanoGPT](https://nano-gpt.com/api) for pay-as-you-go pricing with no monthly fees. NanoGPT provides an Anthropic API-compatible endpoint that works seamlessly with Claude Code. This allows you to: * Use Claude Code with your NanoGPT credits * Access the real Claude models (Sonnet, Opus, Haiku) * Optionally route to 400+ other models (GPT-4o, Gemini, DeepSeek, and more) ## Step 1: Install Claude Code Prerequisites: [Node.js 18 or newer](https://nodejs.org/en/download/) ```bash theme={null} # Install Claude Code npm install -g @anthropic-ai/claude-code # Navigate to your project cd your-awesome-project # Start Claude Code claude ``` If you are not familiar with npm but have Cursor, enter the command below in Cursor and it will guide you through the installation of Claude Code. ``` https://docs.anthropic.com/en/docs/claude-code/overview Help me install Claude Code ``` **Note**: If you encounter permission issues during installation, try using `sudo` (macOS/Linux) or running the command prompt as an administrator (Windows) to re-execute the installation command. ## Step 2: Configure NanoGPT * Go to [NanoGPT API](https://nano-gpt.com/api) * Create an account or log in * Copy your API key Set up environment variables using one of the following methods: **Note**: Some commands show no output when setting environment variables. This is normal as long as no errors appear. Run this command in your terminal: ```bash theme={null} curl -O "https://nano-gpt.com/install/claude_code_nanogpt.sh" && bash ./claude_code_nanogpt.sh ``` The script will prompt for your API key and automatically configure `~/.claude/settings.json`: ```json theme={null} { "env": { "ANTHROPIC_AUTH_TOKEN": "your_nanogpt_api_key", "ANTHROPIC_BASE_URL": "https://nano-gpt.com/api/v1", "API_TIMEOUT_MS": "600000" } } ``` The installer now supports browser-based login. When the script starts, choose: 1. Browser login (recommended) - it will open a verification URL, wait for approval, and then save your key in `~/.claude/settings.json`. 2. Paste API key - manual fallback if you prefer. If a browser does not open automatically, copy the printed URL into your browser and approve the request. For more details on the browser login flow, see [CLI login](https://docs.nano-gpt.com/integrations/cli-login). **macOS & Linux**: Edit `~/.claude/settings.json` (create the file if it doesn't exist): ```json theme={null} { "env": { "ANTHROPIC_AUTH_TOKEN": "your_nanogpt_api_key", "ANTHROPIC_BASE_URL": "https://nano-gpt.com/api/v1" } } ``` **Windows Cmd**: ```cmd theme={null} setx ANTHROPIC_AUTH_TOKEN your_nanogpt_api_key setx ANTHROPIC_BASE_URL https://nano-gpt.com/api/v1 ``` **Windows PowerShell**: ```powershell theme={null} [System.Environment]::SetEnvironmentVariable('ANTHROPIC_AUTH_TOKEN', 'your_nanogpt_api_key', 'User') [System.Environment]::SetEnvironmentVariable('ANTHROPIC_BASE_URL', 'https://nano-gpt.com/api/v1', 'User') ``` Replace `your_nanogpt_api_key` with your actual API key. A new terminal window is required for changes to take effect. ## Step 3: Start Using Claude Code Once configured, start Claude Code in your terminal: ```bash theme={null} cd your-project-directory claude ``` > If prompted with "Do you want to use this API key," select "Yes." Grant Claude Code permission to access files in your folder and you're ready to go. *** ## FAQ ### What Models Does This Use? By default, Claude Code uses the real Claude models through NanoGPT: | Claude Code Model | NanoGPT Routes To | | ----------------- | ----------------- | | Opus | Claude Opus 4.5 | | Sonnet | Claude Sonnet 4.5 | | Haiku | Claude Haiku 4.5 | No model mapping is required. NanoGPT passes the model names directly to Anthropic. ### Can I Use Other Models? Yes. NanoGPT supports 400+ models. You can configure Claude Code to use different models by adding model mappings to your `~/.claude/settings.json`: ```json theme={null} { "env": { "ANTHROPIC_AUTH_TOKEN": "your_nanogpt_api_key", "ANTHROPIC_BASE_URL": "https://nano-gpt.com/api/v1", "ANTHROPIC_DEFAULT_SONNET_MODEL": "openai/gpt-5.6-sol", "ANTHROPIC_DEFAULT_OPUS_MODEL": "anthropic/claude-opus-4.5", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "google/gemini-3-flash-preview" } } ``` Recommended models: | Use Case | Recommended Model | | ------------ | ------------------------------- | | Best overall | , `anthropic/claude-opus-4.5` | | Fast/cheap | `google/gemini-3-flash-preview` | List available models with `GET https://nano-gpt.com/api/v1/models`. ### Configuration Not Working? If your configuration changes don't take effect: 1. **Restart Claude Code**: Close all Claude Code windows, open a new terminal, and run `claude` again 2. **Check JSON syntax**: Ensure your `settings.json` has valid JSON (no trailing commas, proper quotes) 3. **Reset configuration**: Delete `~/.claude/settings.json` and reconfigure from scratch ### How Do I Check My Configuration? Run `/status` inside Claude Code to see your current model configuration. ### Recommended Claude Code Version We recommend using the latest version of Claude Code: ```bash theme={null} # Check current version claude --version # Upgrade to latest claude update ``` NanoGPT is compatible with Claude Code 1.0.0 and newer. ### How Is This Different From Using Anthropic Directly? | Feature | Anthropic Direct | NanoGPT | | ----------- | ----------------------------------- | -------------------------- | | Pricing | Monthly subscription or API credits | Pay-as-you-go, no minimums | | Models | Claude only | Claude + 400 other models | | Billing | Separate Anthropic account | Single NanoGPT account | | Rate limits | Anthropic limits | NanoGPT limits | ### Need Help? * Check your balance at [nano-gpt.com/balance](https://nano-gpt.com/balance) * View API usage at [nano-gpt.com/usage](https://nano-gpt.com/usage) * Create or manage API keys at [nano-gpt.com/api](https://nano-gpt.com/api) * Contact support via the chat widget on [nano-gpt.com](https://nano-gpt.com) # CLI Device Login Source: https://docs.nano-gpt.com/integrations/cli-login Integrate NanoGPT device login into your CLI app # NanoGPT CLI Device Login Integration Guide This guide explains how to integrate device login into your CLI application so users can authenticate without embedding a browser. ## Overview The NanoGPT CLI device login flow works like the GitHub CLI or Claude Code login: 1. Your CLI requests a login code from NanoGPT. 2. User opens a URL in their browser and signs in. 3. User approves the CLI access. 4. Your CLI receives an API key (`sk-nano-...`). ```mermaid theme={null} sequenceDiagram participant CLI participant NanoGPT participant User CLI->>NanoGPT: POST /api/cli-login/start NanoGPT-->>CLI: device_code, user_code, verification_uri_complete CLI->>User: Display verification URL User->>NanoGPT: Opens URL, signs in, approves NanoGPT-->>CLI: key "sk-nano-..." ``` ## Step 1: Start the login flow Make a POST request to initiate login: ```bash theme={null} curl -X POST "https://nano-gpt.com/api/cli-login/start" \ -H "Content-Type: application/json" \ -d '{"client_name": "your-app-name"}' ``` ### Request body | Field | Type | Required | Description | | ------------- | ------ | -------- | ----------------------------------------------------------------------------------------- | | `client_name` | string | No | Your application name (max 64 chars). Used to identify the API key in the user's account. | ### Response ```json theme={null} { "device_code": "0Reyai4sGnBE8em8lMLRxDhC-XtQMC2obf8hnVDUWws", "user_code": "VS8Q-ZY3Q", "verification_uri": "https://nano-gpt.com/cli-login/verify", "verification_uri_complete": "https://nano-gpt.com/cli-login/verify?code=VS8Q-ZY3Q", "expires_in": 600, "interval": 2 } ``` | Field | Description | | --------------------------- | -------------------------------------------------------------------------- | | `device_code` | Secret token for polling. Keep this secure and do not display to the user. | | `user_code` | Human-readable code displayed on the approval page. | | `verification_uri` | Base URL for the user to visit. | | `verification_uri_complete` | Full URL with the code pre-filled. Display this to the user. | | `expires_in` | Seconds until the code expires (600 = 10 minutes). | | `interval` | Recommended polling interval in seconds. | ## Step 2: Direct the user to approve Display the `verification_uri_complete` URL to the user. They should open it in their browser. Example output in your CLI: ``` To authenticate, open this URL in your browser: https://nano-gpt.com/cli-login/verify?code=VS8Q-ZY3Q Waiting for approval... ``` When the user opens the URL: 1. They sign in with their NanoGPT account (Google, Discord, etc.). 2. They see the verification code and click "Approve". 3. They can close the browser and return to the CLI. ## Step 3: Poll for approval Poll the status endpoint until you receive the API key: ```bash theme={null} curl -X POST "https://nano-gpt.com/api/cli-login/poll" \ -H "Content-Type: application/json" \ -d '{"device_code": "0Reyai4sGnBE8em8lMLRxDhC-XtQMC2obf8hnVDUWws"}' ``` ### Response codes | Status | Response | Action | | ------ | ---------------------------------------------- | ----------------------------------------- | | `202` | `{"status": "authorization_pending"}` | Continue polling | | `200` | `{"status": "approved", "key": "sk-nano-..."}` | Success. Store the key | | `410` | `{"status": "expired"}` | Code expired. Restart the flow | | `409` | `{"status": "consumed"}` | Key already delivered. Check your storage | | `404` | `{"error": "invalid_code"}` | Invalid `device_code` | ### Polling example (bash) ```bash theme={null} while true; do response=$(curl -sS -X POST "https://nano-gpt.com/api/cli-login/poll" \ -H "Content-Type: application/json" \ -d "{\"device_code\":\"$DEVICE_CODE\"}") echo "$response" if echo "$response" | grep -q '"status":"approved"'; then API_KEY=$(echo "$response" | jq -r '.key') echo "Success! API key: $API_KEY" break fi if echo "$response" | grep -q '"status":"expired"'; then echo "Code expired. Please restart." exit 1 fi sleep 2 done ``` ## Step 4: Use the API key Once you have the API key, use it for all NanoGPT API requests. List available models: ```bash theme={null} curl "https://nano-gpt.com/api/v1/models" \ -H "Authorization: Bearer sk-nano-..." ``` Chat completions (OpenAI-compatible): ```bash theme={null} curl "https://nano-gpt.com/api/v1/chat/completions" \ -H "Authorization: Bearer sk-nano-..." \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-5.6-sol", "messages": [{"role": "user", "content": "Hello from my CLI!"}], "stream": true }' ``` ## Complete integration example ```python theme={null} import time import requests def nanogpt_login(client_name="my-cli-app"): """Authenticate with NanoGPT using device login flow.""" start_response = requests.post( "https://nano-gpt.com/api/cli-login/start", json={"client_name": client_name} ) start_data = start_response.json() device_code = start_data["device_code"] verification_url = start_data["verification_uri_complete"] interval = start_data["interval"] print("\nTo authenticate, open this URL in your browser:\n") print(f" {verification_url}\n") print("Waiting for approval...") while True: poll_response = requests.post( "https://nano-gpt.com/api/cli-login/poll", json={"device_code": device_code} ) if poll_response.status_code == 200: poll_data = poll_response.json() if poll_data.get("status") == "approved": print("\nAuthentication successful!") return poll_data["key"] elif poll_response.status_code == 410: raise Exception("Login expired. Please try again.") time.sleep(interval) api_key = nanogpt_login("my-awesome-cli") print(f"API Key: {api_key}") ``` ## API key management * API keys created through this flow are named `CLI ()` in the user's account. * Users can view and revoke keys at [https://nano-gpt.com/api](https://nano-gpt.com/api). * If the same `client_name` is used multiple times, the existing key is reused. * Keys do not expire unless manually revoked. ## Error handling | Scenario | How to handle | | ----------------------------- | ------------------------------------------------------------------------------- | | User does not approve in time | Codes expire after 10 minutes. Restart the flow. | | Invalid `device_code` | Check you are using the exact code from `/start`. | | 401 on API requests | Key may have been revoked. Re-authenticate. | | Rate limited | `/start`: 10 req/min, `/poll`: 60 req/min per IP. Use the recommended interval. | ## Security best practices 1. Store the API key securely (OS keychain, encrypted config, or secure credential storage). 2. Never log or display the `device_code`. 3. Handle key revocation by prompting the user to re-authenticate on 401s. 4. Use HTTPS only (`https://nano-gpt.com`). ## Rate limits | Endpoint | Limit | | --------------------------- | ----------------------------- | | `POST /api/cli-login/start` | 10 requests per minute per IP | | `POST /api/cli-login/poll` | 60 requests per minute per IP | ## Summary | Endpoint | Method | Purpose | | ---------------------------- | ------ | --------------------------------------------------- | | `/api/cli-login/start` | POST | Start login, get `device_code` and verification URL | | `/api/cli-login/poll` | POST | Poll for approval status and retrieve API key | | `/cli-login/verify?code=...` | GET | Browser page where the user approves | # Cline Source: https://docs.nano-gpt.com/integrations/cline Using NanoGPT with Cline CLI interface # Using Cline with NanoGPT A quick guide to setting up Cline with NanoGPT's API. ## Setup Instructions 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. In Cline settings, add a new Custom Model with these details: * API Provider: OpenAI Compatible * Base URL: [https://nano-gpt.com/api/v1/](https://nano-gpt.com/api/v1/) * API Key: Your key from step 1 * Model ID: > **Tip:** If Cline is not showing a model's `thinking` output, switch the Base URL to `https://nano-gpt.com/api/v1legacy/`. Cline still expects the legacy streaming shape for parallel thinking/output channels, and this endpoint keeps that compatibility without affecting model availability. That's it! You can now use Cline with every model you can think of. Model names are on our [pricing page](https://nano-gpt.com/pricing) - important ones are , anthropic/claude-opus-4.5, google/gemini-3-flash-preview, or just any other model you fancy. # Codex CLI Source: https://docs.nano-gpt.com/integrations/codex-cli Using OpenAI Codex CLI with NanoGPT # Codex CLI > Use OpenAI's Codex CLI with NanoGPT to access GPT-5.2, Claude Opus 4.5, Gemini 3 Flash Preview, and 400+ AI models through a single API key. [Get started with NanoGPT](https://nano-gpt.com/api) for pay-as-you-go pricing with no monthly fees. Add credits and start coding immediately. NanoGPT provides an OpenAI API-compatible endpoint that works seamlessly with Codex CLI. This allows you to: * Use Codex CLI with your NanoGPT credits * Access OpenAI models (GPT-5.2 and more) without a separate OpenAI account * Route to 400+ other models including Claude Opus 4.5, Gemini 3 Flash Preview, and GLM 4.7 ## Step 1: Install Codex CLI Prerequisites: [Node.js 22 or newer](https://nodejs.org/en/download/) ```bash theme={null} # Install Codex CLI npm install -g @openai/codex # Navigate to your project cd your-awesome-project # Start Codex codex ``` If you are not familiar with npm but have Cursor, you can enter the command in Cursor, and Cursor will guide you through the installation. ``` https://developers.openai.com/codex/cli/ Help me install Codex CLI ``` **Note**: If you encounter permission issues during installation, try using `sudo` (macOS/Linux) or running the command prompt as an administrator (Windows) to re-execute the installation command. ## Step 2: Configure NanoGPT * Go to [NanoGPT API](https://nano-gpt.com/api) * Create an account or log in * Copy your API key Set up Codex using one of the following methods: Run this command in your terminal: ```bash theme={null} curl -O "https://nano-gpt.com/install/codex_nanogpt.sh" && bash ./codex_nanogpt.sh ``` The script will: 1. Prompt for your API key 2. Create `~/.codex/config.toml` with NanoGPT configuration 3. Add `NANOGPT_API_KEY` to your shell profile The installer now supports browser-based login. When the script starts, choose: 1. Browser login (recommended) - it will open a verification URL, wait for approval, and then save your key in `~/.codex/config.toml`. 2. Paste API key - manual fallback if you prefer. If a browser does not open automatically, copy the printed URL into your browser and approve the request. For local or self-hosted testing, `NANOGPT_BASE_URL` keeps login and API traffic on the same origin. To override only the Codex API endpoint, set `NANOGPT_API_BASE_URL` to the complete versioned base URL, including `/api/v1` (for example, `https://api.nano-gpt.example/api/v1`). For more details on the browser login flow, see [CLI login](https://docs.nano-gpt.com/integrations/cli-login). **Step 1**: Create `~/.codex/config.toml`: ```toml theme={null} model_provider = "nanogpt" model = "openai/gpt-5.6-sol" [model_providers.nanogpt] name = "NanoGPT" base_url = "https://api.nano-gpt.com/api/v1" env_key = "NANOGPT_API_KEY" wire_api = "responses" ``` Recent Codex CLI versions require `wire_api = "responses"`. Older examples that use `wire_api = "chat"` will fail on current Codex CLI releases. The dedicated `api.nano-gpt.com` host connects directly to NanoGPT's API infrastructure. Use it for Codex because long-running agent sessions resend increasingly large Responses API payloads, which can exceed the website host's request limit before reaching NanoGPT. **Step 2**: Set your API key as an environment variable: **macOS/Linux** (add to `~/.zshrc` or `~/.bashrc`): ```bash theme={null} export NANOGPT_API_KEY="your_nanogpt_api_key" ``` **Windows Cmd**: ```cmd theme={null} setx NANOGPT_API_KEY your_nanogpt_api_key ``` **Windows PowerShell**: ```powershell theme={null} [System.Environment]::SetEnvironmentVariable('NANOGPT_API_KEY', 'your_nanogpt_api_key', 'User') ``` Replace `your_nanogpt_api_key` with your actual API key. Open a new terminal for changes to take effect. If you just want to override the default OpenAI endpoint without a config file: ```bash theme={null} export OPENAI_BASE_URL="https://api.nano-gpt.com/api/v1" export OPENAI_API_KEY="your_nanogpt_api_key" ``` This method works but does not persist across terminal sessions unless added to your shell profile. ## Step 3: Start Using Codex Once configured, start Codex in your terminal: ```bash theme={null} codex ``` Or specify a model directly: ```bash theme={null} codex --model openai/gpt-5.6-sol ``` *** ## FAQ ### What Models Can I Use? NanoGPT supports 400+ models. Popular choices for Codex: | Use Case | Recommended Model | Config | | ------------ | ------------------------------- | ----------------------------------------- | | Best overall | model = "" | | | Best Claude | `anthropic/claude-opus-4.5` | `model = "anthropic/claude-opus-4.5"` | | Fast/cheap | `google/gemini-3-flash-preview` | `model = "google/gemini-3-flash-preview"` | | Long context | `google/gemini-3-pro-preview` | `model = "google/gemini-3-pro-preview"` | | Open model | `zai-org/glm-4.7` | `model = "zai-org/glm-4.7"` | List available models with `GET https://nano-gpt.com/api/v1/models`. ### How Do I Change Models? **Option 1**: Edit `~/.codex/config.toml`: ```toml theme={null} model = "openai/gpt-5.6-sol" ``` **Option 2**: Use CLI flag: ```bash theme={null} codex --model openai/gpt-5.6-sol ``` ### Configuration Not Working? 1. **Check your API key**: Ensure `NANOGPT_API_KEY` is set ```bash theme={null} echo $NANOGPT_API_KEY ``` 2. **Verify config file**: Check `~/.codex/config.toml` exists and has valid TOML syntax 3. **Open a new terminal**: Environment variable changes require a new shell session 4. **Reset configuration**: Delete `~/.codex/config.toml` and run the setup script again ### How Is This Different From Using OpenAI Directly? | Feature | OpenAI Direct | NanoGPT | | ----------- | ----------------------------------- | ---------------------------------- | | Pricing | Monthly subscription or API credits | Pay-as-you-go, no minimums | | Models | OpenAI only | OpenAI + Claude + 400 other models | | Billing | Separate OpenAI account | Single NanoGPT balance | | Rate limits | OpenAI limits | NanoGPT limits | ### Can I Use Both Claude Code and Codex? Yes. Both can be configured to use NanoGPT simultaneously: * **Claude Code**: Uses `~/.claude/settings.json` with Anthropic API format * **Codex CLI**: Uses `~/.codex/config.toml` with OpenAI API format Both tools share the same NanoGPT credit balance. ### Need Help? * Check your balance at [nano-gpt.com/balance](https://nano-gpt.com/balance) * View API usage at [nano-gpt.com/usage](https://nano-gpt.com/usage) * Create or manage API keys at [nano-gpt.com/api](https://nano-gpt.com/api) * Contact support via the chat widget on [nano-gpt.com](https://nano-gpt.com) # Cursor Source: https://docs.nano-gpt.com/integrations/cursor Using NanoGPT with Cursor AI-powered code editor # Using Cursor with NanoGPT A quick guide to setting up Cursor with NanoGPT's API. ## Setup Instructions 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. In Cursor settings: * Override OpenAI Base URL with: [https://nano-gpt.com/api/v1/](https://nano-gpt.com/api/v1/) * Set the API Key to your NanoGPT API key * Click "+ Add Model" to add any of our models you want to use That's it! You can now use any of our models in Cursor. Available models include , anthropic/claude-opus-4.5, google/gemini-3-flash-preview, and many more. Want to explore available models? Visit our [pricing page](https://nano-gpt.com/pricing). # Droid Source: https://docs.nano-gpt.com/integrations/droid Use NanoGPT with the Droid CLI agent ## Quick start 1. Install the Droid CLI: `curl -fsSL https://app.factory.ai/cli | sh` 2. Grab your NanoGPT API key from [nano-gpt.com/api](https://nano-gpt.com/api). ## Configure NanoGPT as a custom model Add (or merge) the following block into `~/.factory/config.json`: ```json theme={null} { "custom_models": [ { "model_display_name": "GLM 4.7 Nano", "model": "zai-org/glm-4.7", "base_url": "https://nano-gpt.com/api/v1/", "api_key": "xxx", "provider": "generic-chat-completion-api" } ] } ``` * Replace `api_key` with your NanoGPT key and keep it secret. * If you already have other `custom_models`, just append this object instead of removing the existing ones. * Keep the trailing slash on `base_url`—Droid expects it when talking to OpenAI-compatible APIs. ## Use it Start a new terminal and run `droid` to load the config. When prompted for a model, pick **GLM 4.7 Nano** (or whatever name you set in `model_display_name`) to use NanoGPT through Droid. # Fluent Source: https://docs.nano-gpt.com/integrations/fluent Use NanoGPT models and MCP tools in Fluent for macOS # Fluent Integration Use this guide to connect [Fluent](https://fluentmac.app/) to NanoGPT. Fluent can use NanoGPT as a model provider and can also run the NanoGPT MCP server as an integration. ## Add NanoGPT as a model provider 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. Open Fluent Settings and go to **Models**. 3. Under **Partners**, select **NanoGPT**. 4. Enter your NanoGPT API key. 5. Click **Refresh** to load the available NanoGPT models. 6. Choose the model you want to use, then click **Make Default** if you want Fluent to use it by default. NanoGPT selected as a partner model provider in Fluent Fluent shows NanoGPT models in its model list after your key is added. You can switch models from Fluent just like any other provider. Fluent may show a NanoGPT partner discount in the Models screen. Check Fluent for the current discount before choosing a model. ## Add NanoGPT MCP NanoGPT MCP gives Fluent access to NanoGPT tools such as chat, balance checks, image generation, web search, URL scraping, YouTube transcription, and model listing. 1. Open Fluent Settings and go to **Integrations**. 2. Click **Add Integration**, then choose **Configure Manually**. 3. Set the integration name to `NanoGPT`. 4. Set **Type** to **Stdio (Local Process)**. 5. In **Command**, enter: ```bash theme={null} npx ``` 6. In **Args**, enter: ```bash theme={null} -y @nanogpt/mcp ``` 7. Add an environment variable: * Key: `NANOGPT_API_KEY` * Value: your NanoGPT API key 8. Save the integration. NanoGPT MCP configured as a Fluent integration Once saved, Fluent should show the NanoGPT integration as connected. ## Notes * You need Node.js 22.x or later for the NanoGPT MCP server. * If Fluent does not connect, verify that `npx` is available in your shell and that `NANOGPT_API_KEY` is set on the integration. * If you rotate your NanoGPT API key, update it in both the NanoGPT model provider settings and the NanoGPT MCP integration environment variables. # Gemini CLI Source: https://docs.nano-gpt.com/integrations/gemini-cli Use Gemini CLI with NanoGPT via the OpenRouter compatible fork # Gemini CLI > Use Gemini CLI with NanoGPT to access Gemini, GPT, Claude, and 400+ models through a single API key. [Get started with NanoGPT](https://nano-gpt.com/api) for pay-as-you-go pricing with no monthly fees. Add credits and start immediately. Gemini CLI is Google's official command-line interface for Gemini. The official CLI only targets Google endpoints, so use the community fork that adds OpenRouter and OpenAI-compatible support to point it at NanoGPT. ## Step 1: Install the OpenRouter Fork Prerequisites: [Node.js 18 or newer](https://nodejs.org/en/download/) ```bash theme={null} git clone https://github.com/heartyguy/gemini-cli cd gemini-cli git checkout feature/openrouter-support npm install ``` This fork is community maintained. If the branch name changes or is missing, check the repository README for the latest OpenRouter-compatible branch. ## Step 2: Configure NanoGPT * Go to [NanoGPT API](https://nano-gpt.com/api) * Create an account or log in * Copy your API key (starts with `sk-nano-`) Set the base URL and API key in the same shell where you will run the CLI: ```bash theme={null} export OPENROUTER_BASE_URL="https://nano-gpt.com/api/v1" export OPENROUTER_API_KEY="sk-nano-YOUR-API-KEY" ``` ```powershell theme={null} $env:OPENROUTER_BASE_URL = "https://nano-gpt.com/api/v1" $env:OPENROUTER_API_KEY = "sk-nano-YOUR-API-KEY" ``` For persistent configuration, add the exports to your shell profile: ```bash theme={null} echo 'export OPENROUTER_BASE_URL="https://nano-gpt.com/api/v1"' >> ~/.zshrc echo 'export OPENROUTER_API_KEY="sk-nano-YOUR-API-KEY"' >> ~/.zshrc source ~/.zshrc ``` ## Step 3: Start Gemini CLI From the `gemini-cli` folder, launch the CLI: ```bash theme={null} npm start ``` On first launch, the CLI may prompt you to select a theme or complete authentication required by the fork. ## Models Once connected, you can use any model listed on the [NanoGPT pricing page](https://nano-gpt.com/pricing), including: * `google/gemini-3-pro-preview` * `google/gemini-3-flash-preview` * `anthropic/claude-opus-4.5` ## Optional: Verify Your Connection ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/chat/completions" \ -H "Authorization: Bearer sk-nano-YOUR-API-KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "google/gemini-3-flash-preview", "messages": [{"role": "user", "content": "Hello!"}] }' ``` ## Troubleshooting | Issue | Solution | | ----------------------------- | ------------------------------------------------------------------------------ | | `401 Unauthorized` | Check your API key and ensure your account has credits | | `Connection refused` | Verify `OPENROUTER_BASE_URL` is set to `https://nano-gpt.com/api/v1` | | `Model not found` | Confirm the model name at [nano-gpt.com/pricing](https://nano-gpt.com/pricing) | | CLI not recognizing variables | Restart your terminal or set the variables in the same session | # Grok CLI Source: https://docs.nano-gpt.com/integrations/grok-cli Use Grok CLI with NanoGPT and Grok + 50 other models # Grok CLI > Use Grok CLI with NanoGPT to access Grok models and 50+ other models through a single API key. [Get started with NanoGPT](https://nano-gpt.com/api) for pay-as-you-go pricing with no monthly fees. NanoGPT provides an OpenAI-compatible endpoint that works with the Grok CLI. This lets you: * Use Grok CLI with your NanoGPT credits * Access Grok models without a separate xAI account * Use any other NanoGPT model through the same CLI ## Step 1: Install Grok CLI Prerequisites: [Node.js 18 or newer](https://nodejs.org/en/download/) ```bash theme={null} # Install Grok CLI npm install -g @vibe-kit/grok-cli # Verify installation grok --version ``` **Note**: If you encounter permission issues during installation, try using `sudo` (macOS/Linux) or running the command prompt as an administrator (Windows) to re-execute the installation command. ## Step 2: Configure NanoGPT * Go to [NanoGPT API](https://nano-gpt.com/api) * Create an account or log in * Copy your API key Grok CLI reads these environment variables: | Variable | Value | Description | | --------------- | ----------------------------- | ---------------------------------- | | `GROK_BASE_URL` | `https://nano-gpt.com/api/v1` | NanoGPT OpenAI-compatible endpoint | | `GROK_API_KEY` | `sk-nano-YOUR-API-KEY` | Your NanoGPT API key | ```bash theme={null} export GROK_BASE_URL="https://nano-gpt.com/api/v1" export GROK_API_KEY="sk-nano-YOUR-API-KEY" ``` ```powershell theme={null} $env:GROK_BASE_URL = "https://nano-gpt.com/api/v1" $env:GROK_API_KEY = "sk-nano-YOUR-API-KEY" ``` ```cmd theme={null} set GROK_BASE_URL=https://nano-gpt.com/api/v1 set GROK_API_KEY=sk-nano-YOUR-API-KEY ``` Replace `sk-nano-YOUR-API-KEY` with your actual API key. Open a new terminal for changes to take effect if you set persistent variables. Add the variables to your shell profile (for example, `~/.zshrc` or `~/.bashrc`): ```bash theme={null} echo 'export GROK_BASE_URL="https://nano-gpt.com/api/v1"' >> ~/.zshrc echo 'export GROK_API_KEY="sk-nano-YOUR-API-KEY"' >> ~/.zshrc source ~/.zshrc ``` ## Step 3: Start Using Grok CLI Launch Grok CLI with a specific model: ```bash theme={null} grok --model grok-3 ``` Since you are connected to NanoGPT, you can use any available model: ```bash theme={null} # Grok models grok --model grok-3 grok --model grok-3-fast grok --model grok-3-mini # Other providers through NanoGPT grok --model openai/gpt-5.6-sol grok --model anthropic/claude-opus-4.5 grok --model google/gemini-3-flash-preview ``` Model names are listed on our [pricing page](https://nano-gpt.com/pricing). ## Available Grok Models | Model | Description | | ------------- | ----------------------------------- | | `grok-3` | xAI flagship model | | `grok-3-fast` | Faster, more cost-effective version | | `grok-3-mini` | Lightweight version | ## Known Limitations Grok CLI has limited compatibility with thinking or reasoning models. When you use a thinking model, the full reasoning content may be shown in the output. If you want first-party CLI support for NanoGPT, consider [Claude Code](https://docs.nano-gpt.com/integrations/claude-code) or [Codex CLI](https://docs.nano-gpt.com/integrations/codex-cli). ## Troubleshooting | Issue | Solution | | -------------------------------- | ----------------------------------------------------------------------------- | | `401 Unauthorized` | Check your API key is correct and has credits | | `GROK_BASE_URL not set` | Ensure the environment variable is exported | | `Model not found` | Verify the model name at [nano-gpt.com/pricing](https://nano-gpt.com/pricing) | | `Command not found: grok` | Run `npm install -g @vibe-kit/grok-cli` again | | Thinking content flooding output | Use a non-thinking model variant | ## FAQ ### Is Grok CLI official xAI software? No. Grok CLI is a third-party package. NanoGPT works with it by providing an OpenAI-compatible endpoint. ### Can I access Grok models from other clients? Yes. You can use Grok models from Claude Code, Cursor, or any OpenAI-compatible client by setting the base URL to NanoGPT. ## API Details (Reference) | Setting | Value | | ------------------------- | ----------------------------------- | | Base URL | `https://nano-gpt.com/api/v1` | | Chat Completions Endpoint | `POST /chat/completions` | | Models Endpoint | `GET /models` | | Auth Header | `Authorization: Bearer sk-nano-...` | ### Example cURL (verify connection) ```bash theme={null} curl -X POST "https://nano-gpt.com/api/v1/chat/completions" \ -H "Authorization: Bearer sk-nano-YOUR-API-KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "grok-3-fast", "messages": [{"role": "user", "content": "Hello!"}] }' ``` # JanitorAI Source: https://docs.nano-gpt.com/integrations/janitorai Using NanoGPT with JanitorAI's custom API integration # Using JanitorAI with NanoGPT A quick guide to connecting JanitorAI chats to NanoGPT's API. ## Setup Instructions 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. In JanitorAI, open **API Settings** 3. Select **Proxy** as the provider 4. Click **+ Add** under **Proxy Configurations** 5. Configure the proxy with the following values: ```text theme={null} Configuration Name: any label, e.g. NanoGPT Model Name: Model ID, e.g. zai-org/glm-5.1 Proxy URL: https://nano-gpt.com/api/v1/chat/completions API Key: ``` 6. Click **Apply**, then make sure the NanoGPT proxy configuration is marked **Active** JanitorAI now routes your conversations through NanoGPT. ## Available Models Model IDs can be found on the [NanoGPT pricing page](https://nano-gpt.com/pricing). ## Include Reasoning If you want JanitorAI to include reasoning output for reasoning-capable models, duplicate the configuration above and change the proxy URL to: ```text theme={null} https://nano-gpt.com/api/v1thinking/chat/completions ``` ## Troubleshooting If JanitorAI shows a **PROXY ERROR** like this: JanitorAI proxy error saying no response from bot The most common causes are: * You used a paid model that is not included in your subscription and your NanoGPT account has no balance. * The proxy configuration is wrong. Re-check that **Proxy URL** is `https://nano-gpt.com/api/v1/chat/completions` or `https://nano-gpt.com/api/v1thinking/chat/completions`, and that your API key has no extra spaces. # Kilo Code Source: https://docs.nano-gpt.com/integrations/kilocode Using NanoGPT with Kilo Code interface # Using Kilo Code with NanoGPT A quick guide to setting up Kilo Code with NanoGPT's API. ## Setup Instructions 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. In Kilo Code settings, add a new Custom Model with these details: * API Provider: OpenAI Compatible * Base URL: [https://nano-gpt.com/api/v1/](https://nano-gpt.com/api/v1/) * API Key: Your key from step 1 * Model ID: That's it! You can now use Kilo Code with every model you can think of. Model names are on our [pricing page](https://nano-gpt.com/pricing) - important ones are , anthropic/claude-opus-4.5, google/gemini-3-flash-preview, or just any other model you fancy. # LibreChat Source: https://docs.nano-gpt.com/integrations/librechat Using NanoGPT with LibreChat for a ChatGPT-like interface # Using LibreChat with NanoGPT A quick guide to setting up LibreChat with NanoGPT's API. ## Setup Instructions 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. Locate your LibreChat installation's `librechat.example.yml` file 3. Create a copy named `librechat.yml` 4. Add the following configuration to your `librechat.yml`: ```yaml theme={null} # NanoGPT Example - name: 'NanoGPT' apiKey: '${NANO_GPT_API_KEY}' baseURL: 'https://nano-gpt.com/api/v1/' models: default: [ "openai/gpt-5.6-sol", "anthropic/claude-opus-4.5", "google/gemini-3-flash-preview", ] fetch: true addParams: reasoning_content_compat: true titleConvo: true titleModel: 'openai/gpt-5.6-sol' modelDisplayLabel: 'NanoGPT' iconUrl: https://nano-gpt.com/logo.png ``` LibreChat currently streams internal thinking from the legacy `reasoning_content` field. The `addParams` block above enables NanoGPT's compatibility shim so LibreChat can render thoughts without additional changes. If you would rather not send the compatibility flag, you can instead point LibreChat at `https://nano-gpt.com/api/v1legacy/` which keeps the legacy response shape by default. 5. Set your API key in your environment variables: ```bash theme={null} export NANO_GPT_API_KEY='your-api-key-here' ``` That's it! Restart LibreChat, and you'll have access to all NanoGPT models through the interface. ## Available Models You can access all our models through this integration, including: * GPT-5.2 * Claude Opus 4.5 * Gemini 3 Flash Preview * Perplexity * And many more! For a complete list of available models and their pricing, visit our [pricing page](https://nano-gpt.com/pricing). # MCP Source: https://docs.nano-gpt.com/integrations/mcp Use NanoGPT via MCP-compatible clients # NanoGPT MCP Integration The NanoGPT MCP (Model Context Protocol) server allows you to use NanoGPT's AI capabilities directly from MCP-compatible clients like Claude Code, Cursor, and other AI coding assistants. ## Features The NanoGPT MCP server provides the following tools: | Tool | Description | | ---------------------------- | -------------------------------------------------------- | | `nanogpt_chat` | Send chat messages to any AI model available on NanoGPT | | `nanogpt_get_balance` | Check your account balance (USD and Nano) | | `nanogpt_image_generate` | Generate images using DALL-E, Flux, Midjourney, and more | | `nanogpt_web_search` | Search the web for current information | | `nanogpt_scrape_urls` | Extract content from web pages | | `nanogpt_youtube_transcribe` | Get transcripts from YouTube videos | | `nanogpt_list_text_models` | List available text/chat models | | `nanogpt_list_image_models` | List available image generation models | | `nanogpt_list_audio_models` | List available audio models (TTS/STT) | | `nanogpt_list_video_models` | List available video generation models | | `nanogpt_vision` | Analyze images with vision-capable models | ## Prerequisites * A NanoGPT account with an API key ([get one here](https://nano-gpt.com/api)) * Node.js 22.x or later * An MCP-compatible client (Claude Code, Cursor, etc.) ## Installation ### Option 1: Using npx (Recommended) The easiest way to use the NanoGPT MCP server is via `npx`. #### Claude Code **macOS / Linux** ```bash theme={null} claude mcp add nanogpt --scope user \ --env NANOGPT_API_KEY=YOUR_API_KEY \ -- npx -y @nanogpt/mcp ``` **Windows** ```powershell theme={null} claude mcp add nanogpt --scope user --env NANOGPT_API_KEY=YOUR_API_KEY -- cmd /c "C:\Program Files\nodejs\npx.cmd" -y @nanogpt/mcp ``` > On native Windows, `npx` is usually a `.cmd` shim, so it should be launched through `cmd /c`. If you use WSL, run the macOS/Linux command inside WSL instead of the native Windows one. > > Claude Code's MCP CLI syntax has changed over time. If you copied an older NanoGPT command, remove the existing `nanogpt` MCP entry and add it again using the commands on this page. > > An immediate `Invalid API key` error can be caused by a broken MCP launcher command, not just a bad key value. Replace `YOUR_API_KEY` with your actual NanoGPT API key. ### Option 2: Global Installation ```bash theme={null} npm install -g @nanogpt/mcp ``` Then add to your MCP client: ```bash theme={null} claude mcp add nanogpt --scope user \ --env NANOGPT_API_KEY=YOUR_API_KEY \ -- nanogpt-mcp ``` ## Configuration ### Required Environment Variables | Variable | Description | | ----------------- | ------------------------------- | | `NANOGPT_API_KEY` | Your NanoGPT API key (required) | ### Optional Environment Variables | Variable | Default | Description | | ----------------------- | ---------------------------- | ------------------------------------------- | | `NANOGPT_TIMEOUT_MS` | `600000` (10 min) | Request timeout in milliseconds | | `NANOGPT_DEFAULT_MODEL` | `claude-sonnet-4-5-20250929` | Default model for chat requests | | `NANOGPT_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error` | | `NANOGPT_BASE_URL` | `https://nano-gpt.com` | Base URL for the NanoGPT API | | `NANOGPT_AUTH_MODE` | `bearer` | Auth mode: `bearer`, `x-api-key`, or `both` | | `NANOGPT_MAX_RETRIES` | `0` | Number of retry attempts | ## Usage Examples Once configured, you can use NanoGPT tools directly from your MCP client. Here are some examples: ### Check Your Balance Ask your AI assistant: > "Check my NanoGPT balance" The assistant will call `nanogpt_get_balance` and show your USD and Nano balances. ### Chat with AI Models > "Use NanoGPT to ask Claude Sonnet about the best practices for REST API design" ### Generate Images > "Use NanoGPT to generate an image of a futuristic city at sunset" ### Analyze Images > "Use NanoGPT vision to analyze this product photo" ### Search the Web > "Use NanoGPT web search to find the latest news about AI" ### Get YouTube Transcripts > "Use NanoGPT to get the transcript of this YouTube video: [https://www.youtube.com/watch?v=](https://www.youtube.com/watch?v=)..." ### Scrape Web Pages > "Use NanoGPT to scrape the content from [https://example.com](https://example.com)" ## Tool Reference ### nanogpt\_chat Send a chat completion request to an AI model. **Parameters:** * `messages` (required): Array of message objects with `role` ("system", "user", "assistant") and `content` * `model` (optional): Model ID (defaults to Claude Sonnet 4.5) * `temperature` (optional): Sampling temperature (0-2) * `max_tokens` (optional): Maximum tokens in response **Example:** ```json theme={null} { "messages": [ {"role": "user", "content": "Explain quantum computing in simple terms"} ], "model": "openai/gpt-5.6-sol" } ``` ### nanogpt\_get\_balance Check your current account balance. **Parameters:** None **Returns:** * `usd_balance`: Balance in USD * `nano_balance`: Balance in Nano (XNO) * `nanoDepositAddress`: Your Nano deposit address ### nanogpt\_image\_generate Generate images from text prompts. **Parameters:** * `prompt` (required): Text description of the image to generate * `model` (optional): Image model to use (e.g., "dall-e-3", "flux-pro") * `n` (optional): Number of images to generate (1-10) * `size` (optional): Image size (e.g., "1024x1024") * `quality` (optional): Image quality setting **Example:** ```json theme={null} { "prompt": "A serene mountain landscape at dawn", "model": "flux-pro", "size": "1024x1024" } ``` ### nanogpt\_web\_search Search the web for current information. **Parameters:** * `query` (required): Search query * `depth` (optional): "standard" or "deep" * `outputType` (optional): "searchResults", "sourcedAnswer", or "structured" * `includeImages` (optional): Include image results * `fromDate` / `toDate` (optional): Date range filter (YYYY-MM-DD) **Example:** ```json theme={null} { "query": "latest developments in fusion energy", "depth": "deep", "outputType": "sourcedAnswer" } ``` ### nanogpt\_vision Analyze images using vision-capable models. **Parameters:** * `messages` (required): Array of message objects with text and image content blocks * `model` (optional): Vision-capable model ID * `temperature` (optional): Sampling temperature (0-2) * `max_tokens` (optional): Maximum tokens in response **Example:** ```json theme={null} { "messages": [ { "role": "user", "content": [ { "type": "text", "text": "What issues do you see in this image?" }, { "type": "image", "source": { "type": "url", "url": "https://example.com/photo.jpg" } } ] } ], "model": "openai/gpt-5.6-sol" } ``` ### nanogpt\_scrape\_urls Scrape content from web pages. **Parameters:** * `urls` (required): Array of URLs to scrape (max 5 per request) * `mode` (optional): "scrape-page" or "conversation" * `stealthMode` (optional): Use stealth mode for scraping **Example:** ```json theme={null} { "urls": ["https://example.com/article1", "https://example.com/article2"] } ``` ### nanogpt\_youtube\_transcribe Get transcripts from YouTube videos. **Parameters:** * `urls` (required): Array of YouTube URLs (max 10 per request) **Example:** ```json theme={null} { "urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"] } ``` ### nanogpt\_list\_text\_models List available text/chat models. **Parameters:** * `detailed` (optional): Include pricing information (default: false) ### nanogpt\_list\_image\_models List available image generation models. **Parameters:** * `detailed` (optional): Include pricing information (default: true) ### nanogpt\_list\_audio\_models List available audio models. **Parameters:** * `detailed` (optional): Include pricing information (default: true) * `type` (optional): Filter by type: "tts", "stt", or "all" ### nanogpt\_list\_video\_models List available video generation models. **Parameters:** * `detailed` (optional): Include pricing information (default: true) ## Resources Resources allow agents to access data saved during a session: * **`nanogpt://raw/{id}`**: Raw JSON response from a previous API call * **`nanogpt://image/{id}`**: Generated images stored during the session ## Error Handling The MCP server provides clear error messages for common issues: | Error | Meaning | Solution | | -------------------- | ----------------------- | ----------------------------------------------------------- | | Invalid API key | Authentication failed | Check your `NANOGPT_API_KEY` and MCP launch command | | Insufficient balance | Account balance too low | Top up your account at [nano-gpt.com](https://nano-gpt.com) | | Rate limited | Too many requests | Wait and try again | | Timeout | Request took too long | Try a simpler request or increase `NANOGPT_TIMEOUT_MS` | ## Billing * All paid tools deduct from your NanoGPT balance * Use `nanogpt_get_balance` to check your balance before expensive operations * Model listing tools are free to use * Failed requests (timeouts, errors) are not charged ## Troubleshooting ### "Missing required env var: NANOGPT\_API\_KEY" **macOS / Linux** ```bash theme={null} claude mcp add nanogpt --scope user \ --env NANOGPT_API_KEY=your_actual_key_here \ -- npx -y @nanogpt/mcp ``` **Windows** ```powershell theme={null} claude mcp add nanogpt --scope user --env NANOGPT_API_KEY=your_actual_key_here -- cmd /c "C:\Program Files\nodejs\npx.cmd" -y @nanogpt/mcp ``` Make sure you set your API key when adding the MCP server. If you re-add the server later, include `--env NANOGPT_API_KEY=...` again. ### "Invalid API key" errors An immediate `Invalid API key` error can be caused by a broken MCP launcher command, not just a bad key value. 1. Verify your API key at [nano-gpt.com/api](https://nano-gpt.com/api) 2. Make sure you're using the full API key value 3. Check for extra spaces or newlines in the key 4. Verify that your Claude Code MCP launch command is correct, especially on Windows where `npx` should be run via `cmd /c "C:\Program Files\nodejs\npx.cmd"`. ### Requests timing out For long-running operations like web search, you can increase the timeout: **macOS / Linux** ```bash theme={null} claude mcp add nanogpt --scope user \ --env NANOGPT_API_KEY=your_key \ --env NANOGPT_TIMEOUT_MS=900000 \ -- npx -y @nanogpt/mcp ``` **Windows** ```powershell theme={null} claude mcp add nanogpt --scope user --env NANOGPT_API_KEY=your_key --env NANOGPT_TIMEOUT_MS=900000 -- cmd /c "C:\Program Files\nodejs\npx.cmd" -y @nanogpt/mcp ``` ### MCP server not connecting 1. Ensure Node.js 22+ is installed: `node --version` 2. Try removing and re-adding the MCP server 3. Check your MCP client's logs for detailed error messages 4. If you already added NanoGPT using an older command, remove the existing `nanogpt` MCP entry and add it again with the updated command ## Support * **Documentation:** [nano-gpt.com/docs](https://nano-gpt.com/docs) * **Discord:** Join our community for help and updates * **API Keys:** [nano-gpt.com/api](https://nano-gpt.com/api) ## Privacy & Security * Your API key is stored locally and only sent to NanoGPT servers * The MCP server runs locally on your machine * No data is logged or stored by the MCP server * Chat messages and other content are processed according to NanoGPT's [privacy policy](https://nano-gpt.com/privacy) # n8n Source: https://docs.nano-gpt.com/integrations/n8n Use n8n OpenAI nodes with NanoGPT to access 50+ models in your workflows ## Overview n8n can use any OpenAI-compatible API. With NanoGPT, you get: * One API key for 50+ models (GPT-5.2, Claude Opus 4.5, Gemini 3 Flash Preview, and more) * The ability to switch models without changing your workflow logic * Unified billing across providers ## Prerequisites * An n8n instance (desktop app, self-hosted, or n8n Cloud) * A NanoGPT account and API key from [nano-gpt.com/api](https://nano-gpt.com/api) ## Step 1: Get your NanoGPT API key 1. Go to [nano-gpt.com/api](https://nano-gpt.com/api) 2. Click "Create New API Key" 3. Copy the key (format: `sk-nano-...`) 4. Store it securely, you will not be able to view it again ## Step 2: Create OpenAI credentials in n8n 1. Open your n8n instance 2. Go to **Credentials** in the left sidebar 3. Click **+ Add Credential** 4. Search for and select **OpenAI** 5. Fill in the fields: | Field | Value | | --------------- | ---------------------------------- | | Credential Name | `NanoGPT` (or any name you prefer) | | API Key | `sk-nano-YOUR-API-KEY` | | Base URL | `https://nano-gpt.com/api/v1` | 6. Click **Save** ## Step 3: Use NanoGPT in a workflow 1. Go to **Workflows** and click **+ New Workflow** 2. Add an **OpenAI** node or **Chat OpenAI** node 3. In the node settings: * **Credential:** Select your NanoGPT credential * **Model:** Enter the model name exactly (examples below) * Configure your prompt or messages 4. Click **Execute Node** to test ## Available models Use any model listed on the [NanoGPT pricing page](https://nano-gpt.com/pricing). Enter the model name exactly as shown. | Model | Provider | Best for | | ------------------------------- | ------------ | ----------------------- | | OpenAI | Best overall | | | `anthropic/claude-opus-4.5` | Anthropic | Deep reasoning, writing | | `anthropic/claude-sonnet-4.5` | Anthropic | Coding, analysis | | `google/gemini-3-flash-preview` | Google | Fast, cost-effective | | `google/gemini-3-pro-preview` | Google | Long context, reasoning | | `zai-org/glm-4.7` | Zhipu | Open model alternative | ## Example workflows ### Basic chat completion ``` [Manual Trigger] -> [OpenAI Chat] -> [Output] OpenAI Chat node: - Credential: NanoGPT - Model: openai/gpt-5.6-sol - Messages: User message from input ``` ### Document summarization ``` [Webhook] -> [HTTP Request (fetch doc)] -> [OpenAI Chat] -> [Respond to Webhook] OpenAI Chat node: - Model: google/gemini-3-flash-preview - System: Summarize the following document concisely - User: {{ $json.documentContent }} ``` ### Multi-model comparison ``` [Input] -> [OpenAI Chat (GPT-4o)] -\ -> [Merge] -> [Compare Results] [Input] -> [OpenAI Chat (Claude)] -/ ``` ## Troubleshooting | Issue | Solution | | -------------------- | ----------------------------------------------------- | | `401 Unauthorized` | Verify the API key and that your account has credits | | `Invalid API Key` | Ensure there are no extra spaces when copying the key | | `Model not found` | Double-check the model name on the pricing page | | `Connection timeout` | Confirm the Base URL is `https://nano-gpt.com/api/v1` | | `Rate limited` | Add a delay between requests or upgrade your plan | ## Advanced: HTTP Request node If you need full control over parameters, use the HTTP Request node: 1. Add **HTTP Request** 2. Set: * **Method:** `POST` * **URL:** `https://nano-gpt.com/api/v1/chat/completions` * **Authentication:** Header Auth * **Header Name:** `Authorization` * **Header Value:** `Bearer sk-nano-YOUR-API-KEY` * **Body Content Type:** JSON * **Body:** ```json theme={null} { "model": "openai/gpt-5.6-sol", "messages": [ { "role": "user", "content": "{{ $json.prompt }}" } ] } ``` ## Related links * [Get API key](https://nano-gpt.com/api) * [Model pricing](https://nano-gpt.com/pricing) * [n8n documentation](https://docs.n8n.io/) * [n8n OpenAI node docs](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.lmchatopenai/) # OpenClaw (ClawdBot) Source: https://docs.nano-gpt.com/integrations/openclaw Use OpenClaw with NanoGPT to access Claude, GPT, Gemini, and more ## Quick Setup Guide 1. Install OpenClaw (ClawdBot) and skip any default model setup. 2. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api). 3. Open your config (Config -> Models -> Raw, or edit `~/.clawdbot/clawdbot.json`). 4. Add a NanoGPT provider block like this: ```json theme={null} { "models": { "mode": "merge", "providers": { "nanogpt": { "baseUrl": "https://nano-gpt.com/api/v1", "apiKey": "YOUR_NANOGPT_API_KEY_HERE", "auth": "api-key", "api": "openai-completions", "headers": {}, "authHeader": false, "models": [ { "id": "anthropic/claude-opus-4.5", "name": "Claude Opus 4.5", "reasoning": false, "input": ["text"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 200000, "maxTokens": 8192 } ] } } } } ``` 5. Replace `YOUR_NANOGPT_API_KEY_HERE` with your actual key. 6. Restart OpenClaw (or restart the gateway if you are using it). ## Some Models To Try These are examples only. Use the model IDs returned by the models endpoint below. * `anthropic/claude-opus-4.5` * `google/gemini-3-flash-preview` * `minimax/minimax-m2.1` * `moonshotai/kimi-k2-thinking` ## Get The Most Recent Models ```bash theme={null} curl https://nano-gpt.com/api/v1/models ``` If you want filtered lists: * `https://nano-gpt.com/api/subscription/v1/models` (subscription-only) * `https://nano-gpt.com/api/paid/v1/models` (paid-only) ## Verify Your Setup Once OpenClaw is running, confirm requests are flowing by checking your usage at [nano-gpt.com/usage](https://nano-gpt.com/usage). ## Problems? Email support at [support@nano-gpt.com](mailto:support@nano-gpt.com) or use the in-app chat. # OpenCode Source: https://docs.nano-gpt.com/integrations/opencode OpenCode integration for NanoGPT # OpenCode Integration Use NanoGPT with [OpenCode](https://opencode.ai), the open source AI coding agent for your terminal. ## One-Click Setup (Mac/Linux) Run this command in your terminal: ```bash theme={null} curl -fsSL "https://nano-gpt.com/install/opencode_nanogpt.sh" | bash ``` This will: 1. Open your browser to authenticate with NanoGPT (or let you paste an API key) 2. Configure OpenCode to use NanoGPT as your AI provider 3. Set up popular models like Claude Sonnet 4.5, Claude 4.5 Opus, , Gemini 3 Flash (Preview), Gemini 3 Pro, GLM 4.7 ## Manual Setup If you prefer to configure manually: ### 1. Create the auth file Add your NanoGPT API key to `~/.local/share/opencode/auth.json`: ```json theme={null} { "nano-gpt": { "type": "api", "key": "your-nano-gpt-api-key" } } ``` ### 2. Create the config file Create `~/.config/opencode/.opencode.json`: ```json theme={null} { "model": "nano-gpt/claude-sonnet-4-5-20250929", "provider": { "nano-gpt": { "npm": "@ai-sdk/openai-compatible", "name": "NanoGPT", "options": { "baseURL": "https://nano-gpt.com/api/v1" }, "models": { "claude-opus-4-5-20251101": { "name": "Claude 4.5 Opus", "limit": { "context": 200000, "output": 32000 } }, "claude-sonnet-4-5-20250929": { "name": "Claude Sonnet 4.5", "limit": { "context": 1000000, "output": 64000 } }, "openai/gpt-5.6-sol": { "name": "GPT 5.6 Sol", "limit": { "context": 1050000, "output": 128000 } }, "deepseek/deepseek-v4-pro": { "name": "DeepSeek V4 Pro", "limit": { "context": 1048576, "output": 384000 } }, "deepseek/deepseek-v4-pro:thinking": { "name": "DeepSeek V4 Pro Thinking", "limit": { "context": 1048576, "output": 384000 } }, "deepseek/deepseek-v4-flash": { "name": "DeepSeek V4 Flash", "limit": { "context": 1048576, "output": 384000 } }, "deepseek/deepseek-v4-flash:thinking": { "name": "DeepSeek V4 Flash Thinking", "limit": { "context": 1048576, "output": 384000 } }, "google/gemini-3-flash-preview": { "name": "Gemini 3 Flash (Preview)", "limit": { "context": 1048756, "output": 65536 } }, "gemini-3-pro-preview": { "name": "Gemini 3 Pro", "limit": { "context": 1048756, "output": 65536 } }, "zai-org/glm-4.7": { "name": "GLM 4.7", "limit": { "context": 200000, "output": 65535 } } } } } } ``` ## Usage After setup, start OpenCode: ```bash theme={null} opencode ``` ### Switching Models Use the `/model` command inside OpenCode to switch between models, or edit the `model` field in your `.opencode.json`: ```json theme={null} { "model": "nano-gpt/deepseek/deepseek-v4-pro" } ``` ### Available Models The setup script preconfigures these models: | Model ID | Description | | ---------------------------------------------- | --------------------------- | | `nano-gpt/claude-sonnet-4-5-20250929` | Claude Sonnet 4.5 (default) | | `nano-gpt/claude-opus-4-5-20251101` | Claude 4.5 Opus | | nano-gpt/ | | | `nano-gpt/deepseek/deepseek-v4-pro` | DeepSeek V4 Pro | | `nano-gpt/deepseek/deepseek-v4-pro:thinking` | DeepSeek V4 Pro Thinking | | `nano-gpt/deepseek/deepseek-v4-flash` | DeepSeek V4 Flash | | `nano-gpt/deepseek/deepseek-v4-flash:thinking` | DeepSeek V4 Flash Thinking | | `nano-gpt/google/gemini-3-flash-preview` | Gemini 3 Flash (Preview) | | `nano-gpt/gemini-3-pro-preview` | Gemini 3 Pro | | `nano-gpt/zai-org/glm-4.7` | GLM 4.7 | ### Adding More Models Add any of NanoGPT's 250+ models to the `models` section in your config: ```json theme={null} "models": { "zai-org/glm-4.7": { "name": "GLM 4.7", "limit": { "context": 200000, "output": 65535 } } } ``` List available models with `GET https://nano-gpt.com/api/v1/models`. ## Requirements * [OpenCode](https://opencode.ai) installed (`curl -fsSL https://opencode.ai/install | bash`) * A NanoGPT account with API access ## Troubleshooting **OpenCode not finding the config?** OpenCode searches for config in this order: 1. `./.opencode.json` (current directory) 2. `$XDG_CONFIG_HOME/opencode/.opencode.json` 3. `$HOME/.opencode.json` **Authentication errors?** Check that your API key is correctly set in `~/.local/share/opencode/auth.json` (as an object with `type` and `key`) and that the key name matches the provider ID (`nano-gpt`). # OpenHands Source: https://docs.nano-gpt.com/integrations/openhands Using NanoGPT with OpenHands autonomous agent # Using OpenHands with NanoGPT A quick guide to setting up OpenHands with NanoGPT's API. ## Setup Instructions 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. In OpenHands, add a new LLM Endpoint and toggle "Advanced". 3. Configure the fields as follows: * Custom Model: `openai/` * Example: openai/ (OpenHands' first `openai/` selects its OpenAI-compatible adapter; the rest is the NanoGPT model ID.) * If a model name contains a slash, include it: e.g. `openai/anthropic/claude-opus-4.5` or `openai/google/gemini-3-flash-preview` * Base URL: `https://nano-gpt.com/api/v1` (no trailing slash) * API Key: Your key from step 1 * Search API Key (Tavily): Optional * Agent: `CodeActAgent` (recommended) ## Important Notes * The Base URL must NOT end with a `/`. Use `https://nano-gpt.com/api/v1` exactly. * Prefix the model with `openai/` and use the exact model name as shown by the NanoGPT API or on our pricing page. You can now use OpenHands with any NanoGPT model. Popular choices include , `anthropic/claude-opus-4.5`, and `google/gemini-3-flash-preview`. For a full list, check our [pricing page](https://nano-gpt.com/pricing). # OpenWebUI Source: https://docs.nano-gpt.com/integrations/openwebui Using NanoGPT with OpenWebUI for an open-source ChatGPT-like interface ## Quick Setup Guide 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. In OpenWebUI, go to Settings and find the "Add Connection" option 3. Enter the following details: * URL: `https://nano-gpt.com/api/v1` * API Key: Your key from step 1 * Prefix ID: (Optional) Leave empty or add a custom prefix * Model IDs: Leave empty to include all available models That's it! Once you save the connection, you'll have access to all NanoGPT models through OpenWebUI. ## Available Models You can access all our models through this integration, including: * ChatGPT * Claude 3.7 Sonnet * Gemini 2.0 Pro * Perplexity * And many more! For a complete list of available models and their pricing, visit our [pricing page](https://nano-gpt.com/pricing). # Otaku Source: https://docs.nano-gpt.com/integrations/otaku Using NanoGPT with Otaku, a roleplay terminal client # Otaku Integration [Otaku](https://otaku.sh) is a roleplay client for the terminal, available on macOS, Linux, and Windows through WSL. NanoGPT is built in, so your NanoGPT model catalog appears directly in the model picker. ## Setup 1. Install Otaku: ```bash theme={null} curl -LsSf https://otaku.sh/install.sh | sh ``` Alternatively, install it with `uv tool install otaku`. 2. Run `otaku`. When the model picker opens, press **Tab** to move to the provider panel, select **NanoGPT**, and paste your NanoGPT API key into the `API key:` field. Otaku verifies the key and lists your NanoGPT model catalog on the left. Your API key is stored encrypted in the configuration file. 3. Choose a model and start chatting. The prompt shows `$` while a cloud model is selected. ## Useful commands * `/model` (Ctrl+O) reopens the picker to switch models or providers. * `/balance` shows your NanoGPT balance. * `/context` shows exactly what is sent to the model. ## Manual configuration To configure Otaku manually, add your API key to the `[nanogpt]` section of `~/.otaku/configs/providers.toml`: ```toml theme={null} [nanogpt] url = "https://nano-gpt.com/api/v1" api_key = "your-nanogpt-api-key" ``` For more information, see the [Otaku guide and demo](https://otaku.sh) or the [Otaku source code](https://github.com/enclavum/otaku). # RisuAI Source: https://docs.nano-gpt.com/integrations/risuai Using NanoGPT with RisuAI for chat and character-based conversations # RisuAI Integration Use this guide to connect RisuAI to NanoGPT so you can run your chats through NanoGPT models. ## Before You Start 1. Create or log in to your NanoGPT account. 2. Generate your API key at [nano-gpt.com/api](https://nano-gpt.com/api). 3. Open [risuai.xyz](https://risuai.xyz/). If RisuAI shows onboarding, skip it. NanoGPT is configured in the main settings. ## Step-by-Step Setup 1. From the RisuAI home screen, open the left menu (hamburger icon), then go to **Settings**. Open the menu and go to Settings in RisuAI 2. In **Settings**, open the **Chat Bot** panel. Open the Chat Bot settings panel in RisuAI 3. Set both of these fields to **NanoGPT**: * **Model** * **Auxiliary Model** Set both Model and Auxiliary Model to NanoGPT in RisuAI 4. Paste your key from [nano-gpt.com/api](https://nano-gpt.com/api) into **NanoGPT API Key**. 5. If you have a NanoGPT subscription, enable **Use subscription endpoint & models**. * If this is not enabled, RisuAI uses pay-as-you-go mode by default. Enable subscription endpoint and select NanoGPT model settings in RisuAI 6. Choose your model in **NanoGPT Model**: * **Select from List** to browse available models. * **Manual Input** if the model list does not load or you want to enter a model ID directly. 7. Optional: enable **Response Streaming** for streamed output. ## Pay-As-You-Go Provider Selection In pay-as-you-go mode, some models expose multiple providers. RisuAI will show provider options with pricing so you can compare and choose. Compare provider options and pricing in RisuAI pay-as-you-go mode ## Quick Troubleshooting * Model list not loading: switch to **Manual Input** and enter the model ID. * Unexpected pricing mode: verify whether **Use subscription endpoint & models** is enabled. * Auth errors: confirm your NanoGPT API key is valid and pasted without extra spaces. # Roo Code Source: https://docs.nano-gpt.com/integrations/roocode Using NanoGPT with Roo Code interface # Using Roo Code with NanoGPT A quick guide to setting up Roo Code with NanoGPT's API. ## Setup Instructions 1. Get your API key from [nano-gpt.com/api](https://nano-gpt.com/api) 2. In Roo Code settings, add a new Custom Model with these details: * API Provider: OpenAI Compatible * Base URL: [https://nano-gpt.com/api/v1thinking/](https://nano-gpt.com/api/v1thinking/) * API Key: Your key from step 1 * Model ID: > **Tip:** Roo Code surfaces the `thinking` and `response` channels side-by-side. Pointing it at `https://nano-gpt.com/api/v1thinking/` keeps that richer reasoning stream intact for models like Minimax M2 or Kimi K2 Thinking. That's it! You can now use Roo Code with every model you can think of. Model names are on our [pricing page](https://nano-gpt.com/pricing) - important ones are , anthropic/claude-opus-4.5, google/gemini-3-flash-preview, or just any other model you fancy. # SillyTavern Source: https://docs.nano-gpt.com/integrations/sillytavern Using NanoGPT with SillyTavern for character-based chat # SillyTavern Integration Configure NanoGPT: * Select "OpenAI" as the API type (NanoGPT uses an OpenAI-compatible API) * Enter your NanoGPT API key * Set the API URL to: `https://nano-gpt.com/api/v1` (SillyTavern understands the modern `delta.reasoning` stream) * Set the Model to: (or `anthropic/claude-opus-4.5`, `google/gemini-3-flash-preview`) **Important Note**: If you choose the Chat Completions endpoint in SillyTavern, you will see NanoGPT automatically appear as an option. # TypingMind Source: https://docs.nano-gpt.com/integrations/typingmind Using NanoGPT with TypingMind for an enhanced ChatGPT experience ## Quick Start Guide 1. Get your API key from your [API page](https://nano-gpt.com/api) 2. In [TypingMind](https://www.typingmind.com/), go to add custom model 3. Select "Create Manually" 4. Enter the following details: * Name: NanoGPT * API Type: OpenAI Compatible API * Icon URL (Optional): [https://nano-gpt.com/logo.png](https://nano-gpt.com/logo.png) * Endpoint: `https://nano-gpt.com/api/v1/chat/completions` * Model ID: (or anthropic/claude-opus-4.5, google/gemini-3-flash-preview) * Authorization: Bearer YOUR\_API\_KEY 5. Click "Test" to verify the connection, then "Add Model" ## All the models We have a v1 models route that now supports detailed model information including pricing when you use `?detailed=true`. You can also find an easy overview of all models on our [pricing page](https://nano-gpt.com/pricing). # Introduction Source: https://docs.nano-gpt.com/introduction Welcome to the Nano-GPT.com API ## Overview The NanoGPT API allows you to generate text, images and video using any AI model available. Our implementation for text generation generally matches the OpenAI standards. All examples in this documentation also work on our alternative domains. Just replace the base URL `https://nano-gpt.com` with your preferred domain: [ai.bitcoin.com](https://ai.bitcoin.com), [bcashgpt.com](https://bcashgpt.com), or [cake.nano-gpt.com](https://cake.nano-gpt.com). Only the base URL changes; endpoints and request formats remain the same. ## Main API Endpoints | Endpoint | Purpose | | ---------------------------------------------- | ----------------------------------------------------------------------- | | `POST /api/v1/chat/completions` | OpenAI-compatible chat generation | | `POST https://api.nano-gpt.com/api/v1/batches` | Asynchronous Batch API jobs for Chat Completions and Responses requests | | `POST /api/v1/responses` | OpenAI-compatible Responses API | | `POST /v1/images/generations` | OpenAI-compatible image generation | | `POST /api/generate-video` | Video generation, editing, extension, and upscaling | | `POST /api/v1/messages` | Anthropic-compatible Messages API | For selected endpoints, you can also use [Accountless x402 API Payments](/api-reference/miscellaneous/x402) to receive a payment quote without an account or API key by including `x-x402: true` on the initial unauthenticated quote request. Check `GET /api/v1/x402/endpoints` for the current supported matrix. ## Chat Completion example Here's a simple python example using our OpenAI-compatible chat completions endpoint: ```python theme={null} import requests import json BASE_URL = "https://nano-gpt.com/api/v1" API_KEY = "YOUR_API_KEY" # Replace with your API key headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "Accept": "text/event-stream" # Required for SSE streaming } def stream_chat_completion(messages, model="minimax/minimax-m2.7"): """ Send a streaming chat completion request using the OpenAI-compatible endpoint. """ data = { "model": model, "messages": messages, "stream": True # Enable streaming } response = requests.post( f"{BASE_URL}/chat/completions", headers=headers, json=data, stream=True ) if response.status_code != 200: raise Exception(f"Error: {response.status_code}") for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith('data: '): line = line[6:] if line == '[DONE]': break try: chunk = json.loads(line) if chunk['choices'][0]['delta'].get('content'): yield chunk['choices'][0]['delta']['content'] except json.JSONDecodeError: continue # Example usage messages = [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Please explain the concept of artificial intelligence."} ] try: print("Assistant's Response:") for content_chunk in stream_chat_completion(messages): print(content_chunk, end='', flush=True) print("") except Exception as e: print(f"Error: {str(e)}") ``` ## Quick Start The quickest way to get started with our API is to explore our [Endpoint Examples](/api-reference/endpoint/chat-completion). Each endpoint page provides comprehensive documentation with request/response formats and example code. The [Chat Completion](/api-reference/endpoint/chat-completion) endpoint is a great starting point for text generation. ## Documentation Sections For detailed documentation on each feature, please refer to the following sections: * [Text Generation](/api-reference/text-generation) - Complete guide to text generation APIs including OpenAI-compatible endpoints and legacy options * [Image Generation](/api-reference/image-generation) - Learn how to generate images using various models like Recraft, Flux, and Stable Diffusion. * [Video Generation](/api-reference/video-generation) - Create high-quality videos with our video generation API # Partner Program Source: https://docs.nano-gpt.com/partner/overview Add NanoGPT-powered AI to your product while keeping your own users, brand, and UI — then apply to join. ## Overview The NanoGPT Partner Program lets you add AI — chat, image, video, and audio — inside your own product while NanoGPT runs the models, balances, and billing behind the scenes. Your app stays the customer-facing experience; your users never need a NanoGPT account or API key. Authentication is a short-lived JWT your backend signs and NanoGPT verifies against your public key. There is no OAuth dance, no SDK to install, and no NanoGPT credentials to manage. Most partners make their first successful call the same day they apply. Already approved and ready to integrate? See the [Partner Auth](/api-reference/miscellaneous/partner-auth) reference for the full JWT spec, scopes, and endpoints. ## What You Get * **Keep your users in your product.** Your app remains the brand and UI. NanoGPT sits behind it as the AI, model, balance, and billing layer. * **User-funded balances.** Each linked user has their own NanoGPT balance and can top up with Nano, stablecoins (USDC/USDT), major chains (SOL, ETH, and more), or other supported crypto. * **No model or billing infrastructure to build.** Access the full NanoGPT model catalog through one integration; we handle routing, billing, and usage records. * **Optional referral revenue.** Earn a configurable share of your users' top-ups. * **Optional partner tiers.** Offer tiered usage discounts to your users once tiers are enabled for your integration. * **Demo mode to test free.** We activate you in demo mode first, so you can build and test the entire flow against a free model before anything involves money. ## Apply to Become a Partner Applying takes about 15 minutes on your side. You will need three things: A short, lowercase identifier, for example `acme`. This becomes your JWT issuer and the slug we configure for you. Create an ECDSA P-256 keypair and keep the private key in your own secret manager: ```bash theme={null} openssl ecparam -name prime256v1 -genkey -noout -out nanogpt-partner.pem openssl pkey -in nanogpt-partner.pem -pubout ``` Send us the output of the second command (the **public** key) plus a key ID such as `acme-2026-06`. Your public key is safe to share — it can only *verify* tokens, never sign them. The private key never leaves your infrastructure. We confirm the key's fingerprint with you before activation, so it cannot be tampered with in transit. Never send a private key to anyone. Any combination of: AI requests, user balances, top-ups, and signed-in web access (SSO). This determines your scopes and browser redirect allowlist. Email these three items to **[partner@nano-gpt.com](mailto:partner@nano-gpt.com?subject=NanoGPT%20Partner%20Program%20application)** and we'll take it from there. ## What Happens Next 1. We register your public key and activate you in **demo mode** — usually the same day. 2. Your backend signs a JWT and calls NanoGPT with it as a Bearer token. Your first request works against a free model with zero balance, so you can validate the full integration before money is involved. 3. When you're ready to launch, we switch off demo mode and confirm your commercial settings — referral revenue share, any partner tiers, and your redirect allowlist. 4. Your users fund their own usage, and you can surface balance and top-up options directly in your product. ## Build the Integration Once you're approved, the [Partner Auth](/api-reference/miscellaneous/partner-auth) reference covers everything you need: the JWT header and claims, the available scopes, sending AI requests, checking balances, creating top-ups, browser SSO login links, reading usage, and error handling. # Quickstart Source: https://docs.nano-gpt.com/quickstart Start querying any model within 2 minutes. ## Get your API key Generate an API key on our [API page](https://nano-gpt.com/api). ## Add Balance If you haven't deposited yet, add some funds to [your balance](https://nano-gpt.com/balance). Minimum deposit is just \$1, or \$0.10 when using crypto. ### API usage examples Here's a simple example using our OpenAI-compatible chat completions endpoint: ```python theme={null} import requests import json BASE_URL = "https://nano-gpt.com/api/v1" API_KEY = "YOUR_API_KEY" # Replace with your API key headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "Accept": "text/event-stream" # Required for SSE streaming } def stream_chat_completion(messages, model="minimax/minimax-m2.7"): """ Send a streaming chat completion request using the OpenAI-compatible endpoint. """ data = { "model": model, "messages": messages, "stream": True # Enable streaming } response = requests.post( f"{BASE_URL}/chat/completions", headers=headers, json=data, stream=True ) if response.status_code != 200: raise Exception(f"Error: {response.status_code}") for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith('data: '): line = line[6:] if line == '[DONE]': break try: chunk = json.loads(line) if chunk['choices'][0]['delta'].get('content'): yield chunk['choices'][0]['delta']['content'] except json.JSONDecodeError: continue # Example usage messages = [ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Please explain the concept of artificial intelligence."} ] try: print("Assistant's Response:") for content_chunk in stream_chat_completion(messages): print(content_chunk, end='', flush=True) print("") except Exception as e: print(f"Error: {str(e)}") ``` #### Routing preferences For supported open-source models, you can request routing preferences with model suffixes: ```json theme={null} { "model": "zai-org/glm-5:fast", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "zai-org/glm-5:cheap", "messages": [{ "role": "user", "content": "Hello" }] } { "model": "moonshotai/kimi-k2.6:thinking:caching", "messages": [{ "role": "user", "content": "Hello" }] } ``` Use `:fast` for fastest estimated completion, `:cheap` for lowest provider price, and `:caching` to require a cache-capable provider. These are pay-as-you-go provider-selection requests. See [Model Suffixes](/api-reference/miscellaneous/model-suffixes) for the full list. For more detailed examples and other text generation endpoints, check out our [Text Generation Guide](/api-reference/text-generation). #### OpenAI-Compatible Endpoint (v1/images/generations) You can also generate images using our OpenAI-compatible endpoint: ```bash theme={null} curl https://nano-gpt.com/v1/images/generations \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "hidream", "prompt": "A serene landscape at sunset", "n": 1, "size": "1024x1024" }' ``` Here's an example using the OpenAI-compatible endpoint in Python: ```python theme={null} import base64 import requests API_KEY = "YOUR_API_KEY" def generate_image(prompt, model="hidream", size="1024x1024"): response = requests.post( "https://nano-gpt.com/v1/images/generations", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" }, json={ "model": model, "prompt": prompt, "n": 1, "size": size, "response_format": "b64_json" } ) response.raise_for_status() return response.json() # Example usage prompt = "A serene landscape with mountains and a lake at sunset, digital art style" result = generate_image(prompt) image_bytes = base64.b64decode(result["data"][0]["b64_json"]) with open("generated_image.png", "wb") as f: f.write(image_bytes) print("Image generated successfully!") print("Image saved as 'generated_image.png'") ``` For more detailed examples and other image generation options, check out our [Image Generation Guide](/api-reference/image-generation). Submit a video generation job, then poll the status endpoint until the video is ready: ```bash theme={null} RUN_ID=$(curl -s -X POST https://nano-gpt.com/api/generate-video \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "veo2-video", "prompt": "A cinematic shot of a mountain lake at sunrise" }' | jq -r '.runId') curl "https://nano-gpt.com/api/video/status?requestId=$RUN_ID" \ -H "Authorization: Bearer YOUR_API_KEY" ``` For more detailed examples and model-specific options, check out our [Video Generation Guide](/api-reference/video-generation). # URL Parameters (Web App) Source: https://docs.nano-gpt.com/web-app/url-parameters Currently supported URL parameters for the NanoGPT web app UI. This document lists the **currently supported** URL parameters for the NanoGPT web app UI. These are intended for browser shortcuts, OpenSearch integration, and sharing prefilled prompts. > Note: Only documented parameters are guaranteed to keep working. Undocumented parameters may change without notice. ## Conversation URLs **Base URL** * `/conversation/new` **Aliases** * `/conversation` -> redirects to `/conversation/new` * `/ask` -> redirects to `/conversation/new` ### Parameters * `ask` - Prefills the prompt **and auto-submits** it when the page loads. * Use `encodeURIComponent()` for the value. * This is the closest equivalent to a `?q=` style parameter. * `model` - Selects a model by model id. * If the model is a known image/video/audio model, the mode is switched automatically. * `mode` - Sets the UI mode (only if `model` does not override it). * Allowed values: `chat`, `image`, `video`, `audio`, `voice`. * `online` - Enables web search when set to `true`. * Any other value is ignored. * `source` - Attribution/referral tag. * `source=translate` also disables web search for that session. * `transcriptId` - Internal handoff key used by `/youtube` and `/scrape` to inject large prompts. * Requires a pre-stored value in session storage or the shared content cache; not intended for manual use. ### Examples ```text theme={null} /conversation/new?ask=Hello%20from%20the%20address%20bar ``` ```text theme={null} /conversation/new?model=&ask=Summarize%20this%20page ``` ```text theme={null} /conversation/new?mode=image&model= ``` ```text theme={null} /conversation/new?online=true&ask=What%27s%20new%20today%3F ``` ```text theme={null} /conversation/new?source=opensearch&ask=Define%20vector%20search ``` ### Notes * `model` takes precedence over `mode` (a model selection can switch the mode). * There is **no** separate `q` parameter today - use `ask` instead. * Web search provider and depth are **not** configurable via URL; they follow your Settings. ## Media URLs **Base URL** * `/media` **Mode shortcuts** * `/media/image` -> redirects to `/media?mode=image` * `/media/video` -> redirects to `/media?mode=video` * `/media/audio` -> redirects to `/media?mode=audio` ### Parameters * `mode` - Selects the media mode. * Allowed values: `image`, `video`, `audio`. * `model` - Selects a media model by id. * If the model matches a known image/video/audio model, `mode` is switched automatically. * When `audioMode=stt`, `model` may also be a speech-to-text model id. * `audioMode` - Selects audio sub-mode (only in `mode=audio`). * Allowed values: `tts`, `stt`. * `prompt` - Prefills the prompt and is **consumed** on load (removed from the URL). ### Examples ```text theme={null} /media?mode=image&model= ``` ```text theme={null} /media?mode=video&model=&prompt=Make%20this%20cinematic ``` ```text theme={null} /media?mode=audio&audioMode=stt&model= ``` ### Notes * `model` takes precedence over `mode` (a model selection can switch the mode). * If a model id is unknown or incompatible with the selected mode, it is ignored. * For speech-to-text, include both `audioMode=stt` and a compatible `model` id. ## OpenSearch Integration An OpenSearch descriptor is available at: ```text theme={null} /opensearch.xml ``` If you are wiring this into a custom browser keyword/search engine, use the conversation URL with `ask`: ```text theme={null} /conversation/new?source=opensearch&ask={searchTerms} ``` (Replace `{searchTerms}` with the URL-encoded user query.)