Skip to content

API Access Methods

arcadedb-embedded runs the ArcadeDB engine inside your Python process and talks to it through direct JVM calls via JPype. That is the primary mode and the reason the package exists.

It also bundles an optional in-process HTTP server with the Studio web UI. It is off until you call create_server(), and it lets the process that owns the database expose that same data over HTTP to other processes and languages. See Server Mode for what it costs on disk (about +8 MB) and at runtime (nothing until you start it).

So there are three ways to reach your data, and they compose:

How you call it Who can reach it
Embedded (Java API) Direct JVM calls, no network This process only
Server-managed Direct JVM calls, database owned by a running server This process, plus HTTP clients
HTTP API REST + JSON over a socket Any process, any language, optionally remote

Java API (Embedded Mode)

Direct JVM method calls via JPype for embedded/local runtime access.

DSL-first guidance

Prefer SQL/OpenCypher via db.command(...) and db.query(...) for schema, CRUD, and graph operations. Wrapper APIs remain available as compatibility/reference features, but examples and guides are standardized on DSL usage.

Characteristics

  • Transport: Direct JVM method calls (no network)
  • Performance: Fastest (no serialization/network overhead)
  • Use Cases: Single-process applications, high-performance scenarios
  • Setup: Nothing beyond pip install arcadedb-embedded — no server, no Java installation

Example

import arcadedb_embedded as arcadedb

# Direct database access - NO server needed
with arcadedb.create_database("./mydb") as db:
    # Create schema (auto-transactional)
    db.command("sql", "CREATE DOCUMENT TYPE Person")
    db.command("sql", "CREATE PROPERTY Person.name STRING")
    db.command("sql", "CREATE PROPERTY Person.age INTEGER")

    # Insert data (requires transaction)
    with db.transaction():
        db.command("sql", "INSERT INTO Person SET name = 'Alice', age = 30")

    # Query data (SQL is fine for reads)
    result = db.query("sql", "SELECT FROM Person WHERE age > 25")
    for record in result:
        print(f"Name: {record.get('name')}")

Server-Managed Database (Optional):

A server-managed database is still reached through direct JVM calls from the owning process, so reads and writes here cost the same as embedded mode. The difference is that the server also publishes it over HTTP.

import arcadedb_embedded as arcadedb

# Server manages databases (still Java API calls)
server = arcadedb.create_server("./server_data", "password123")
server.start()

try:
    # "mydb" will be created at ./server_data/databases/mydb
    db = server.create_database("mydb")

    # Schema operations are auto-transactional
    db.command("sql", "CREATE DOCUMENT TYPE Person")
    db.command("sql", "CREATE PROPERTY Person.name STRING")
    db.command("sql", "CREATE PROPERTY Person.age INTEGER")

    # Data operations require explicit transactions
    with db.transaction():
        db.command("sql", "INSERT INTO Person SET name = 'Alice', age = 30")

    result = db.query("sql", "SELECT FROM Person WHERE age > 25")
    for record in result:
        print(f"Name: {record.get('name')}")

finally:
    server.stop()

HTTP API (Server Mode)

REST requests over HTTP - enables remote access and multi-language support.

Characteristics

  • Transport: HTTP requests with JSON payloads
  • Performance: Slower than embedded (socket round-trip + JSON on both ends)
  • Use Cases: Multi-process applications, web services, remote access
  • Setup: Requires a running server (create_server(), or the official ArcadeDB server distribution)

Example

import arcadedb_embedded as arcadedb
import requests
from requests.auth import HTTPBasicAuth

# Start server (using Java API)
server = arcadedb.create_server("./server_data", "password123")
server.start()

try:
    # Get server details
    base_url = f"http://localhost:{server.get_http_port()}"
    auth = HTTPBasicAuth("root", "password123")

    # Create database via HTTP (server-level command)
    response = requests.post(
        f"{base_url}/api/v1/server",
        auth=auth,
        json={"command": "CREATE DATABASE mydb"}
    )
    if not response.ok:
        raise RuntimeError(f"Server command failed: {response.status_code} {response.text}")

    # Create schema via HTTP
    response = requests.post(
        f"{base_url}/api/v1/command/mydb",
        auth=auth,
        json={"language": "sql", "command": "CREATE DOCUMENT TYPE Person"}
    )
    if not response.ok:
        raise RuntimeError(f"Create type failed: {response.status_code} {response.text}")

    # Insert data via HTTP
    response = requests.post(
        f"{base_url}/api/v1/command/mydb",
        auth=auth,
        json={
            "language": "sql",
            "command": "INSERT INTO Person SET name = 'Alice', age = 30"
        }
    )
    if not response.ok:
        raise RuntimeError(f"Insert failed: {response.status_code} {response.text}")
    # Note: HTTP commands are auto-transactional per request. For multi-statement atomicity, use
    # the HTTP transactional endpoints or embedded `with db.transaction():` blocks.

    # Query data via HTTP
    response = requests.post(
        f"{base_url}/api/v1/query/mydb",
        auth=auth,
        json={"language": "sql", "command": "SELECT FROM Person WHERE age > 25"}
    )
    result = response.json()

    for record in result.get("result", []):
        print(f"Name: {record.get('name')}")

    # Optional: inspect server info (includes available languages)
    response = requests.get(
        f"{base_url}/api/v1/server",
        auth=auth,
    )
    server_info = response.json()
    print("Available languages:", server_info.get("languages"))

finally:
    server.stop()

The first HTTP request is much slower than the rest

Measured on one developer machine, in-process: first request 5.6 s, second 0.7 s, every one after that under 10 ms. Undertow and the REST handlers class-load lazily and the root password is verified with a deliberately expensive KDF, and both land on request one. If you poll for readiness after start(), give the first attempt a generous timeout — a tight one just turns warmup into a failure.

Token-based authentication (optional)

For repeated requests, you can exchange Basic Auth for a session token and use Authorization: Bearer <token> instead of sending credentials each time:

# Login to receive a token
response = requests.post(
    f"{base_url}/api/v1/login",
    auth=auth,
)
token = response.json()["token"]

# Use Bearer token for subsequent requests
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(
    f"{base_url}/api/v1/query/mydb",
    headers=headers,
    json={"language": "sql", "command": "SELECT FROM Person"}
)

Hybrid Usage

Both APIs can be used simultaneously on the same server, against the same database, in one transaction-consistent store. This is the case in-process server mode exists for: local code keeps full-speed embedded access while other processes reach the same data over HTTP.

import arcadedb_embedded as arcadedb
import requests
from requests.auth import HTTPBasicAuth

# Start server
server = arcadedb.create_server("./hybrid", "password123")
server.start()

try:
    # Create database using Java API (fastest)
    db = server.create_database("hybriddb")

    # Schema operations are auto-transactional
    db.command("sql", "CREATE DOCUMENT TYPE Person")
    db.command("sql", "CREATE PROPERTY Person.name STRING")
    db.command("sql", "CREATE PROPERTY Person.age INTEGER")

    # Data operations require explicit transactions
    with db.transaction():
        db.command("sql", "INSERT INTO Person SET name = 'Alice', age = 30")

    # Query same data using HTTP API (remote access)
    auth = HTTPBasicAuth("root", "password123")
    response = requests.post(
        f"http://localhost:{server.get_http_port()}/api/v1/query/hybriddb",
        auth=auth,
        json={"language": "sql", "command": "SELECT FROM Person"}
    )
    if not response.ok:
        raise RuntimeError(f"HTTP query failed: {response.status_code} {response.text}")

    result = response.json()
    print(f"HTTP API found {len(result['result'])} records")
    print(f"Record from HTTP: {result['result'][0]}")

finally:
    server.stop()

Example 23 (examples/23_server_mode_http_access.py) is a runnable version of this: it writes through embedded access and reads the same rows back over HTTP, then writes over HTTP and reads back embedded.

Cost of each access path

Embedded access is faster than HTTP for the obvious reason: no socket, no JSON encode/decode, no auth check per call. How much faster depends entirely on your payload shape and result size, so this guide deliberately does not print a ratio — measure your own workload.

What is worth knowing structurally:

Aspect Embedded / server-managed HTTP API
Transport In-JVM call TCP + HTTP + JSON
Result materialization Python objects from JVM refs Parse a JSON body
Transaction scope Any block you want (with db.transaction():) One request, unless you use the transactional endpoints
Reach Owning process only Any process, any language, optionally remote
First-call cost JVM start (once) Server warmup, ~5.6 s measured (once)

For a measured, reproducible comparison of Python-side call overhead in the embedded path, see benchmarks/jpype_overhead/REPORT.md in the repository.

When to Use Each

Use Embedded Mode When:

  • Single Python process application
  • Maximum performance required
  • Local SQL/OpenCypher workflows
  • Batch processing
  • Local development/testing

Use HTTP API When:

  • Multi-process architecture
  • Remote database access
  • Web applications/APIs
  • Multiple programming languages
  • Microservices architecture
  • Cross-network access
  • You want Studio on live data

Use Hybrid Access When:

  • Local high-performance operations + remote monitoring
  • Hybrid applications with embedded + web components
  • Development (embedded) + production monitoring (HTTP API)

Use the official ArcadeDB server distribution when:

In-process server mode ties the server's lifetime to your Python process. When that is wrong for you, run the standalone server instead:

docker run -d --name arcadedb -p 2480:2480 -p 2424:2424 \
  -e JAVA_OPTS="-Darcadedb.server.rootPassword=playwithdata" \
  arcadedata/arcadedb:latest

Choose it when you need a server that outlives any one client, HA/replication, TLS termination, or the other wire protocols (Postgres, Redis, Mongo, Gremlin). To move data across, use export_database / SQL IMPORT DATABASE — the on-disk format and export archives are compatible.

Common Misconceptions

  • "Embedded mode is only for Java"
    • ✅ Embedded mode is Python calling Java via JPype (fully Pythonic)
  • "Embedded means no SQL"
    • ✅ Full SQL, OpenCypher, vector search, and graph algorithms run in-process
  • "HTTP API is inferior"
    • ✅ HTTP API enables remote access (different purpose)
  • "Must choose one or the other"
    • ✅ Both can be used simultaneously on the same server
  • "Performance difference means HTTP is broken"
    • ✅ Performance difference is expected (network vs direct calls)
  • "I need a separate server to use Studio on my embedded data"
    • create_server() serves Studio from the same process that holds the database