Quickstart
This walks through the whole path once: a project and its secret, an end user and their token,
a session with a first message, and its reply streaming back. Every call is plain HTTPS with
JSON, so any language works; the examples use curl, JavaScript and Python.
1. Create a project and a secret
Sign in to the console with your Google account and create a project. The console shows the project's first secret once, right after you create the project: copy it into your backend's secret store. You can create more secrets, and revoke any of them, on the project's Keys page.
The console's project page also shows the API base URL to call. The examples below read both from the environment:
export NEWEYES_API_BASE="https://…" # the API base URL from the console
export NEWEYES_PROJECT_SECRET="…" # the secret you just copied
A project secret acts for your whole project. Keep it on your servers: never put it in an app, a web page, or a code repository.
2. Create an end user and issue a token
Your backend creates one end user per user of your product, and keeps the returned id with its
own record of that user. Every call creates a new user, so call it once per user. Ask for the
user's first token in the same call with issue_token:
- curl
- JavaScript
- Python
curl -s "$NEWEYES_API_BASE/v1/users" -X POST \
-H "Authorization: Bearer $NEWEYES_PROJECT_SECRET" \
-H "Content-Type: application/json" \
-d '{"issue_token": true}' \
| jq -r '.id, .token.token' # the user's id, then its token string
const response = await fetch(`${process.env.NEWEYES_API_BASE}/v1/users`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NEWEYES_PROJECT_SECRET}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ issue_token: true }),
});
const user = await response.json();
const userToken = user.token.token; // the token string
import os, requests
api = os.environ["NEWEYES_API_BASE"]
secret = os.environ["NEWEYES_PROJECT_SECRET"]
user = requests.post(
f"{api}/v1/users",
headers={"Authorization": f"Bearer {secret}"},
json={"issue_token": True},
).json()
user_token = user["token"]["token"] # the token string
{
"object": "user",
"id": "4f6d…",
"is_active": true,
"created_at": "2026-10-19T08:00:00Z",
"token": { "object": "token", "token": "…", "token_type": "bearer", "expires_at": "2026-10-19T09:00:00Z" }
}
The token string is the answer's token.token: it is what the user's app sends, as
Authorization: Bearer <token>.
A token lasts an hour unless you ask for another lifetime (expires_in, from 60 seconds to 24
hours). When it runs out, issue the user a new one:
curl -s "$NEWEYES_API_BASE/v1/users/$USER_ID/tokens" -X POST \
-H "Authorization: Bearer $NEWEYES_PROJECT_SECRET" \
-H "Content-Type: application/json" \
-d '{"expires_in": 3600}'
{ "object": "token", "token": "…", "token_type": "bearer", "expires_at": "2026-10-19T10:00:00Z" }
Hand the token string to your app. From here on, every call is made as that user, with the token; the examples read it from the environment:
export NEWEYES_USER_TOKEN="…" # the token string: token.token in the answer above
3. Start a session with a first message
A session is one conversation. Create it with the user's first message, and pick the form its
replies take: markdown for formatted text, or a2ui (the default) for UI data your app renders.
- curl
- JavaScript
- Python
curl -s "$NEWEYES_API_BASE/v1/sessions" -X POST \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"output_format": "markdown", "text": "Plan a two-day trip to Kyoto in spring."}'
const api = process.env.NEWEYES_API_BASE;
const auth = { Authorization: `Bearer ${process.env.NEWEYES_USER_TOKEN}` };
const created = await fetch(`${api}/v1/sessions`, {
method: 'POST',
headers: { ...auth, 'Content-Type': 'application/json' },
body: JSON.stringify({
output_format: 'markdown',
text: 'Plan a two-day trip to Kyoto in spring.',
}),
});
const session = await created.json();
token = os.environ["NEWEYES_USER_TOKEN"]
auth = {"Authorization": f"Bearer {token}"}
session = requests.post(
f"{api}/v1/sessions",
headers=auth,
json={"output_format": "markdown", "text": "Plan a two-day trip to Kyoto in spring."},
).json()
{
"object": "session",
"id": "9c1e…",
"type": "text",
"status": "queued",
"output_format": "markdown",
"task_id": "exec-…",
"last_event_id": "0",
"created_at": "2026-10-19T08:01:00Z"
}
The message started a task: the assistant's answer, named by task_id. last_event_id says
where the task begins on the session's event stream.
4. Stream the reply
Open the session's event stream with Last-Event-ID set to that last_event_id. The stream
sends the task from its first frame: the reply text as it is written (text.delta), the steps
the assistant takes (task.progress), and one end frame (task.completed, task.failed or
task.stopped) whose text is the whole reply.
- curl
- JavaScript
- Python
curl -N "$NEWEYES_API_BASE/v1/sessions/$SESSION_ID/events" \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN" \
-H "Last-Event-ID: 0"
// EventSource cannot send an Authorization header, so read the stream with fetch.
const stream = await fetch(`${api}/v1/sessions/${session.id}/events`, {
headers: { ...auth, 'Last-Event-ID': session.last_event_id },
});
const reader = stream.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
let end;
while ((end = buffer.indexOf('\n\n')) !== -1) {
const block = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
const fields = Object.fromEntries(
block
.split('\n')
.filter((line) => line && !line.startsWith(':'))
.map((line) => [line.slice(0, line.indexOf(':')), line.slice(line.indexOf(':') + 2)]),
);
if (!fields.event) continue; // a `: ping` keeps the connection alive
const data = JSON.parse(fields.data);
if (fields.event === 'text.delta') process.stdout.write(data.delta);
if (['task.completed', 'task.failed', 'task.stopped'].includes(fields.event)) {
reader.cancel();
}
}
}
import json
with requests.get(
f"{api}/v1/sessions/{session['id']}/events",
headers={**auth, "Last-Event-ID": session["last_event_id"]},
stream=True,
) as stream:
event = None
for line in stream.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event = line[len("event: "):]
elif line.startswith("data: ") and event:
data = json.loads(line[len("data: "):])
if event == "text.delta":
print(data["delta"], end="", flush=True)
if event in ("task.completed", "task.failed", "task.stopped"):
break
A frame looks like this on the wire:
id: 1760860860123-0:1
event: text.delta
data: {"task_id": "exec-…", "delta": "Day one: start early at Fushimi Inari"}
You can also ask for the task on the request itself: send the next message with
"stream": true, and the response is that task's own frames, ending with its end frame. See
Event streams.
5. Read the conversation
The reply is also kept in the session's message list, so your app can show the conversation later without replaying the stream:
curl -s "$NEWEYES_API_BASE/v1/sessions/$SESSION_ID/messages" \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN"
Next steps
- Sessions and messages — later messages, attachments, stopping a task, and deleting a session.
- Event streams — every frame, resuming after a dropped connection, and heartbeats.
- Errors — how a refused call answers, and how to branch on it.