Copilot Chat
curl --request POST \
--url https://app.nouvel.ai/api/v1/copilot/chat \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '
{
"messages": [
{
"messages[].role": "<string>",
"messages[].content": "<string>"
}
],
"productUrl": "<string>",
"projectId": "<string>"
}
'import requests
url = "https://app.nouvel.ai/api/v1/copilot/chat"
payload = {
"messages": [
{
"messages[].role": "<string>",
"messages[].content": "<string>"
}
],
"productUrl": "<string>",
"projectId": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({
messages: [{'messages[].role': '<string>', 'messages[].content': '<string>'}],
productUrl: '<string>',
projectId: '<string>'
})
};
fetch('https://app.nouvel.ai/api/v1/copilot/chat', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.nouvel.ai/api/v1/copilot/chat",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'messages' => [
[
'messages[].role' => '<string>',
'messages[].content' => '<string>'
]
],
'productUrl' => '<string>',
'projectId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.nouvel.ai/api/v1/copilot/chat"
payload := strings.NewReader("{\n \"messages\": [\n {\n \"messages[].role\": \"<string>\",\n \"messages[].content\": \"<string>\"\n }\n ],\n \"productUrl\": \"<string>\",\n \"projectId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.nouvel.ai/api/v1/copilot/chat")
.header("Authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"messages\": [\n {\n \"messages[].role\": \"<string>\",\n \"messages[].content\": \"<string>\"\n }\n ],\n \"productUrl\": \"<string>\",\n \"projectId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.nouvel.ai/api/v1/copilot/chat")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"messages\": [\n {\n \"messages[].role\": \"<string>\",\n \"messages[].content\": \"<string>\"\n }\n ],\n \"productUrl\": \"<string>\",\n \"projectId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"response": "<string>",
"model": "<string>"
}AI Copilot
Copilot Chat
AI-powered creative assistance for ad strategy, captions, and ideation
POST
/
api
/
v1
/
copilot
/
chat
Copilot Chat
curl --request POST \
--url https://app.nouvel.ai/api/v1/copilot/chat \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '
{
"messages": [
{
"messages[].role": "<string>",
"messages[].content": "<string>"
}
],
"productUrl": "<string>",
"projectId": "<string>"
}
'import requests
url = "https://app.nouvel.ai/api/v1/copilot/chat"
payload = {
"messages": [
{
"messages[].role": "<string>",
"messages[].content": "<string>"
}
],
"productUrl": "<string>",
"projectId": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({
messages: [{'messages[].role': '<string>', 'messages[].content': '<string>'}],
productUrl: '<string>',
projectId: '<string>'
})
};
fetch('https://app.nouvel.ai/api/v1/copilot/chat', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.nouvel.ai/api/v1/copilot/chat",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'messages' => [
[
'messages[].role' => '<string>',
'messages[].content' => '<string>'
]
],
'productUrl' => '<string>',
'projectId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.nouvel.ai/api/v1/copilot/chat"
payload := strings.NewReader("{\n \"messages\": [\n {\n \"messages[].role\": \"<string>\",\n \"messages[].content\": \"<string>\"\n }\n ],\n \"productUrl\": \"<string>\",\n \"projectId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.nouvel.ai/api/v1/copilot/chat")
.header("Authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"messages\": [\n {\n \"messages[].role\": \"<string>\",\n \"messages[].content\": \"<string>\"\n }\n ],\n \"productUrl\": \"<string>\",\n \"projectId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.nouvel.ai/api/v1/copilot/chat")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"messages\": [\n {\n \"messages[].role\": \"<string>\",\n \"messages[].content\": \"<string>\"\n }\n ],\n \"productUrl\": \"<string>\",\n \"projectId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"response": "<string>",
"model": "<string>"
}Send messages to the Nouvel AI copilot for help with ad strategy, caption writing, product research, and creative ideation. This is the API version of the dashboard copilot — it returns a complete JSON response instead of a server-sent event stream.
Authentication
string
required
Your Nouvel API key with
copilot:chat permission. Format: Bearer nvl_xxxxRequest Body
array
required
string
Product URL for competitor ad research. When provided, the copilot fetches competitor ad intelligence to inform its responses.
string
UUID of an active project. When provided, the copilot has context about the project’s settings, scenes, scripts, and style configuration.
Response
string
required
The copilot’s full text response.
string
required
The AI model used (determined by your plan, not configurable via API).
Example Requests
curl -X POST https://app.nouvel.ai/api/v1/copilot/chat \
-H "Authorization: Bearer nvl_xxxx" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "user", "content": "Write me 3 Instagram captions for a protein powder targeting gym-goers" }
]
}'
curl -X POST https://app.nouvel.ai/api/v1/copilot/chat \
-H "Authorization: Bearer nvl_xxxx" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "user", "content": "What angles are competitors using for this product?" }
],
"productUrl": "https://example.com/products/whey-protein"
}'
curl -X POST https://app.nouvel.ai/api/v1/copilot/chat \
-H "Authorization: Bearer nvl_xxxx" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "user", "content": "Write me a caption for my protein powder" },
{ "role": "assistant", "content": "Here are some caption ideas..." },
{ "role": "user", "content": "Make the second one shorter and add emojis" }
]
}'
const response = await fetch('https://app.nouvel.ai/api/v1/copilot/chat', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.NOUVEL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: [
{ role: 'user', content: 'Write me 3 Instagram captions for a protein powder' },
],
productUrl: 'https://example.com/products/whey-protein',
}),
});
const { response: answer, model } = await response.json();
console.log(`Copilot (${model}): ${answer}`);
import requests
response = requests.post(
'https://app.nouvel.ai/api/v1/copilot/chat',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
},
json={
'messages': [
{'role': 'user', 'content': 'Write me 3 Instagram captions for a protein powder'}
],
'productUrl': 'https://example.com/products/whey-protein',
}
)
data = response.json()
print(data['response'])
Response Examples
{
"response": "Here are 3 Instagram captions for your protein powder:\n\n1. **The Gains Caption**: \"Every scoop brings you closer to your goals. 25g of pure whey protein, zero compromises. 💪 #FitnessJourney #ProteinPowder\"\n\n2. **The Lifestyle Caption**: \"Morning routine upgrade: coffee ☕ + [Brand] protein shake = unstoppable. What's in your post-workout shake?\"\n\n3. **The Social Proof Caption**: \"10,000+ athletes trust [Brand] for their recovery. Join the movement. Link in bio. 🏋️\"",
"model": "balanced"
}
{
"error": "daily_limit_reached",
"resetAt": "2026-03-10T00:00:00.000Z",
"count": 100,
"limit": 100
}
{
"error": "Invalid API key"
}
{
"error": "This API key does not have the 'copilot:chat' permission"
}
Error Codes
| Code | Description |
|---|---|
| 400 | Missing messages array or invalid format |
| 401 | Invalid or missing API key |
| 402 | Plan doesn’t include API access |
| 403 | API key missing copilot:chat permission |
| 429 | Daily message limit reached. Check resetAt for when the limit resets (midnight UTC) |
| 500 | Internal server error |
Context Injection
The copilot automatically enriches its responses with context from your account:| Context Source | When Included |
|---|---|
| Brand kit | Always — business name, description, tone of voice, target audience, color palette, visual style, guardrails |
| Available avatars | Always — platform actors + custom avatars you’ve uploaded |
| Scraped URLs | When the last user message contains product URLs — they’re scraped, classified, and only product pages are included |
| Competitor ads | When productUrl is provided — fetches competitor ad intelligence from our ad research database |
| Project context | When projectId is provided — includes project style settings, scenes, and scripts |
Daily Limits
The copilot enforces a daily message limit per user, determined by your plan. The limit resets at midnight UTC. When the limit is reached, the API returns a429 response with:
resetAt— ISO timestamp when the limit resetscount— number of messages used todaylimit— maximum messages allowed per day
Differences from Dashboard Copilot
| Feature | Dashboard | API |
|---|---|---|
| Auth | Browser session (cookies) | API key |
| Response format | Server-Sent Events (SSE stream) | JSON (complete response) |
| File attachments | Supported | Not supported |
| Product context | Full form (actor, language, images) | productUrl parameter only |
| Max duration | N/A (streaming) | 60 seconds |
The API copilot returns the complete response as JSON, making it ideal for automation and integration workflows where you need the full response before proceeding.
Best Practices
Include conversation history
Include conversation history
The copilot is stateless. To maintain context across turns, include all previous messages in the
messages array. This lets the copilot refine and build on previous responses.Use productUrl for competitive insights
Use productUrl for competitive insights
When asking about ad strategy or creative angles, include a
productUrl to give the copilot access to competitor ad intelligence.Monitor daily limits
Monitor daily limits
Check the
X-RateLimit-Remaining header to track your API rate limit, and handle 429 responses gracefully with the resetAt timestamp.
