Use the Genie One API

Important

This feature is in Beta. A workspace admin must turn on the Genie One API preview. See Requirements.

The Genie One API lets your own application, backend service, or agent ask Genie One natural-language questions about your data over HTTP and stream the answers back. Genie One works across all the data the caller can access and grounds its answers in Genie Ontology, so you don't need to set up a curated agent first. New questions run with the caller's Unity Catalog permissions, so answers include only data the caller can access. Reading a saved response or conversation history returns the stored content. It doesn't rerun queries or recheck permissions on the underlying data.

This page explains how to authenticate, ask questions, stream answers, and manage conversations. For complete request and response formats, see the Genie One API reference.

Requirements

  • A workspace admin must turn on the Genie One API preview from the workspace Previews page.
  • The caller must be able to use Genie One. The Consumer access entitlement or any higher workspace entitlement is enough. See What is consumer access?.
  • The caller must have SELECT privileges on the Unity Catalog data that their questions use.
  • To run queries on a specific SQL warehouse, the caller must have at least CAN USE permission on that warehouse. If you don't specify a warehouse, Genie One chooses one.

Authenticate

The Genie One API accepts OAuth access tokens with the genie scope.

  • For applications where a user is present, use OAuth for users (OAuth U2M). Requests run as that user, so answers reflect the user's own Unity Catalog permissions.
  • For automated workloads, use a service principal with OAuth for service principals (OAuth M2M). The service principal must have access to the data and SQL warehouses its questions use.

You also need your workspace URL. The examples on this page use <workspace-url> for it. See Get identifiers for workspace objects.

Ask a question and stream the answer

Send a question to POST /api/2.0/genie/one/responses. The endpoint returns a text/event-stream response that streams Genie One's progress and final answer as Server-Sent Events (SSE).

Start a conversation

Omit conversation_id to start a new conversation. Set input to the question:

curl

curl -N -X POST "https://<workspace-url>/api/2.0/genie/one/responses" \
  -H "Authorization: Bearer <access-token>" \
  -H "Content-Type: application/json" \
  -d '{"input": "How did revenue change last quarter?"}'

The -N option turns off output buffering so that curl prints each event as it arrives.

Python

The following example uses the Databricks SDK for Python only to resolve your authentication, so it works with OAuth U2M, OAuth M2M, or a configuration profile. It sends the request with the requests library, hands the response and conversation IDs to an on_created callback as soon as the response is accepted, and recovers with a GET request if the stream ends early. It then prints the final answer.

Install the packages that the example uses:

pip install databricks-sdk requests
import json
import time

import requests
from databricks.sdk import WorkspaceClient

w = WorkspaceClient()
base_url = f"{w.config.host}/api/2.0/genie/one"
terminal_events = ("response.completed", "response.failed", "response.incomplete")


def get_response(conversation_id, response_id, timeout_seconds=600):
    # Poll the stored response until it reaches a terminal status.
    deadline = time.time() + timeout_seconds
    while True:
        resp = requests.get(
            f"{base_url}/conversations/{conversation_id}/responses/{response_id}",
            headers=w.config.authenticate(),
        )
        resp.raise_for_status()
        response = resp.json()
        if response["status"] != "in_progress" or time.time() > deadline:
            return response
        time.sleep(3)


def ask(question, conversation_id=None, on_created=None):
    body = {"input": question}
    if conversation_id:
        body["conversation_id"] = conversation_id

    created = None
    try:
        with requests.post(
            f"{base_url}/responses",
            headers={**w.config.authenticate(), "Content-Type": "application/json"},
            json=body,
            stream=True,
        ) as resp:
            resp.raise_for_status()
            # The stream is UTF-8, but its content type doesn't declare a charset.
            resp.encoding = "utf-8"
            for line in resp.iter_lines(decode_unicode=True):
                if not line or not line.startswith("data:"):
                    continue
                # SSE allows an optional single space after "data:".
                data = line[len("data:"):]
                if data.startswith(" "):
                    data = data[1:]
                if data == "[DONE]":
                    break
                event = json.loads(data)
                if event["type"] == "response.created":
                    created = event["response"]
                    if on_created:
                        on_created(created["conversation_id"], created["id"])
                elif event["type"] in terminal_events:
                    return event["response"]
    except requests.RequestException:
        # Without response.created, you can't tell whether the request was accepted.
        if created is None:
            raise
    if created is None:
        raise RuntimeError("The stream ended before the response was accepted.")
    # The stream ended or disconnected before a terminal event. Recover with GET.
    return get_response(created["conversation_id"], created["id"])


response = ask(
    "How did revenue change last quarter?",
    on_created=lambda conversation_id, response_id: print(
        f"conversation_id={conversation_id} response_id={response_id}"
    ),
)
print(response["status"])
for item in response["output"]:
    if item.get("phase") == "final_answer":
        # Keep parts separate so Markdown tables and source notes stay on their own lines.
        print("\n\n".join(part["text"] for part in item["content"]))

This example waits for the terminal event. To show progress while Genie One works, also handle the output item events, as described in Read the event stream.

Read the event stream

Each event has an SSE event name and a JSON data body with a matching type and an increasing sequence_number. A shortened stream looks like this:

event: response.created
data: {"type":"response.created","sequence_number":0,"response":{"object":"response","id":"9f7c84c6d3af4ae8ac5cc43590a15366","model":"genie-one","status":"in_progress","output":[],"conversation_id":"6a34b9a50cb4435da7c463130c9d42c1"}}

event: response.output_item.added
data: {"type":"response.output_item.added","sequence_number":1,"output_index":0,"item":{"type":"message","role":"assistant","status":"in_progress","phase":"final_answer","content":[{"type":"output_text","text":"Revenue increased 12% last quarter.","annotations":[]}]}}

event: response.output_item.done
data: {"type":"response.output_item.done","sequence_number":2,"output_index":0,"item":{"type":"message","role":"assistant","status":"completed","phase":"final_answer","content":[{"type":"output_text","text":"Revenue increased 12% last quarter.","annotations":[]}]}}

event: response.completed
data: {"type":"response.completed","sequence_number":3,"response":{"object":"response","id":"9f7c84c6d3af4ae8ac5cc43590a15366","status":"completed","output":[...],"conversation_id":"6a34b9a50cb4435da7c463130c9d42c1"}}

data: [DONE]

Handle the events as follows:

  • response.created: The response is accepted. Save response.id and response.conversation_id. You need them to ask a follow-up question or to recover if the stream disconnects.
  • response.output_item.added, response.output_item.updated, and response.output_item.done: An assistant message was added, changed, or finished. Each event carries the full message, so replace the message at output_index instead of appending text to it.
  • response.completed, response.failed, or response.incomplete: The response reached a terminal state. The response.output array holds the final assistant messages. The stream then ends with data: [DONE].

Each assistant message has a phase. Messages with the commentary phase describe Genie One's progress while it works, and you can show them as status updates. The message with the final_answer phase is the answer. Answer text is Markdown and can include result tables.

If the response fails after it's accepted, the HTTP status is still 200, and the stream ends with a response.failed event. The response.error object describes the failure. See Troubleshooting.

Ask a follow-up question

To continue a conversation, pass the conversation_id from response.created. Genie One uses the earlier questions and answers as context.

curl -N -X POST "https://<workspace-url>/api/2.0/genie/one/responses" \
  -H "Authorization: Bearer <access-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Break that down by region.",
    "conversation_id": "<conversation-id>"
  }'

You can also set warehouse_id to run queries on a specific SQL warehouse, and user_timezone to an IANA time zone such as America/Los_Angeles so that Genie One interprets relative dates in the user's time zone.

Recover from a dropped connection

Disconnecting from the stream doesn't cancel the response. Genie One keeps working, and you can retrieve the result with the IDs from response.created:

curl "https://<workspace-url>/api/2.0/genie/one/conversations/<conversation-id>/responses/<response-id>" \
  -H "Authorization: Bearer <access-token>"

The request returns the same Response object as the terminal stream event. While status is in_progress, poll the endpoint every few seconds. Stop when status is completed, failed, or incomplete.

Don't resend the POST request to recover. Each POST request starts a new response.

Read citations

Each output_text part has an annotations array that lists the sources Genie One used for that text. Each annotation includes:

  • title: The display name of the source.
  • asset_type: The kind of source, such as TABLE, NOTEBOOK, or WEB.
  • identifier: The asset's identifier, a volume path, or a web URL.
  • index: The one-based citation number that the answer text refers to.

Manage conversations

Conversations that you create with the Genie One API belong to the calling user or service principal. They also appear in that user's chat history in Genie One.

  • List conversations: GET /api/2.0/genie/one/conversations returns the caller's Genie One conversations, most recently updated first. It doesn't include Genie Agent conversations. Use page_size (default 20, maximum 100) and pass next_page_token as page_token to get the next page.
  • Get a conversation: GET /api/2.0/genie/one/conversations/{conversation_id} returns the conversation's title and timestamps, without its messages.
  • List conversation items: GET /api/2.0/genie/one/conversations/{conversation_id}/items returns the user and assistant messages in the conversation. Use it to rebuild a chat history in your application.
  • Delete a conversation: DELETE /api/2.0/genie/one/conversations/{conversation_id} deletes the conversation.

For example, to list your three most recent conversations:

curl "https://<workspace-url>/api/2.0/genie/one/conversations?page_size=3" \
  -H "Authorization: Bearer <access-token>"

Limitations

  • Each request accepts one question as text, up to 25,000 UTF-8 bytes. Images, files, and multiple messages in one request aren't supported.
  • A conversation can have only one active response. A new question in that conversation returns HTTP 409 until the active response finishes.
  • Requests are rate limited per workspace. Requests above the limit return HTTP 429.

Best practices

  • Save the IDs from response.created: Store the response and conversation IDs as soon as the stream starts, so that you can recover the answer if the connection drops.
  • Retry rejected requests with exponential backoff: When the API returns an error response before the stream starts, such as 429, retry with an increasing delay between attempts. After you receive response.created, retrieve the response with GET instead of resending the POST request.
  • Don't blindly retry after a timeout or gateway error: If a request times out or returns a gateway error such as 502 or 504 before response.created arrives, the API might still have accepted it. Resending the POST request can then create a second response.
  • Retry failed responses only when the error is retryable: When a response fails, check response.error.retryable before you ask the question again.
  • Start a new conversation for each session: Reusing one conversation across unrelated sessions can reduce accuracy because Genie One uses earlier turns as context.
  • Log requests and responses: Log response IDs, statuses, and errors to help with debugging and monitoring usage.

Troubleshooting

Symptom Cause and fix
404 with the message "The requested resource was not found." The Genie One API preview is off for the workspace, a conversation or response ID is wrong or belongs to another user, or the warehouse isn't available to the caller. Ask a workspace admin to check the workspace Previews page, then check the IDs and the caller's permissions.
401 or 403 The request has no valid token, or the caller can't add a response to the conversation. Check your OAuth token and its genie scope. See Authenticate.
400 with INVALID_PARAMETER_VALUE The request body has an unknown field or unsupported option, more than one message, or a question longer than 25,000 bytes. Send only the fields in the API reference.
409 with ALREADY_EXISTS Another response is active in the conversation. Wait for it to finish, then try again.
429 with RESOURCE_EXHAUSTED The workspace exceeded the rate limit. Retry with exponential backoff. See Limitations.
The stream ends with response.failed The response failed after it was accepted. Read response.error.message, and retry only if response.error.retryable is true.
The stream disconnects before a terminal event Retrieve the response with the IDs from response.created. See Recover from a dropped connection.

Pricing

The Genie One API runs Chat in Genie One, so requests are billed the same way as Genie One chat. See Genie pricing.

Additional resources