Canonical page: https://docs.singlebase.io/quickstart

# Make your first API request

Create a document and read it back using the same endpoint. This guide runs from your **backend or terminal** with a server key. Keep server and agent keys out of browser code.

## 1. Prepare your project

In the [Singlebase console](https://console.singlebase.io), choose your project and create or select a `notes` collection. Copy the project API endpoint and an enabled server key from project settings.

Set the credentials in your terminal environment or secret manager:

```sh
export SINGLEBASE_API_URL='https://v1.api.singlebase.io/api/<ENDPOINT_KEY>'
export SINGLEBASE_SERVER_KEY='sk_<SERVER_KEY>'
```

Replace the placeholders with your own values. The snippets below use `notes`; use your actual collection name if it differs.

## 2. Check the request format

Every operation is a JSON `POST` to the same URL. The operation name goes in `operation`; its parameters go inside `payload`.

```sh
curl --fail-with-body "$SINGLEBASE_API_URL" \
  -H 'Content-Type: application/json' \
  -H "X-API-KEY: $SINGLEBASE_SERVER_KEY" \
  --data '{"operation":"ping","payload":{}}'
```

Ping returns `data: "pong"`. It checks connectivity and request handling; it does not validate your project's credentials. The next request uses your project and collection.

## 3. Create a document

```sh
curl --fail-with-body "$SINGLEBASE_API_URL" \
  -H 'Content-Type: application/json' \
  -H "X-API-KEY: $SINGLEBASE_SERVER_KEY" \
  --data '{"operation":"data.insert","payload":{"collection":"notes","data":{"title":"First note","status":"active"}}}'
```

The created record receives an `_id`. With this server key and no bearer token, it has no user owner unless you assign an existing user with payload-level `_user_id`. See [Ownership](/data.md#ownership).

## 4. Read it back

```sh
curl --fail-with-body "$SINGLEBASE_API_URL" \
  -H 'Content-Type: application/json' \
  -H "X-API-KEY: $SINGLEBASE_SERVER_KEY" \
  --data '{"operation":"data.query","payload":{"collection":"notes","filter":{"title":"First note"},"limit":1}}'
```

Queries return an array in `data`, even with `limit: 1`. Use the returned `_id` for an exact lookup:

```json
{
  "operation": "data.query",
  "payload": {
    "collection": "notes",
    "filter": { "_id": "<RETURNED_DOCUMENT_ID>" },
    "limit": 1
  }
}
```

## JavaScript SDK example

Install the existing JavaScript SDK:

```sh
npm install @singlebase/singlebase-sdk
```

Run this on your backend with the environment variables above. `baseUrl` is the complete project endpoint; omit `urlAccessKey` because it is already in that URL. Passing `bearer: null` keeps these calls under the server key.

```javascript
import { SinglebaseClient } from "@singlebase/singlebase-sdk";

const sbc = SinglebaseClient({
  baseUrl: process.env.SINGLEBASE_API_URL,
  apiKey: process.env.SINGLEBASE_SERVER_KEY,
});

const created = await sbc.data.insert({
  collection: "notes",
  data: { title: "First note", status: "active" },
}, { bearer: null });
console.log(created);

const notes = await sbc.data.query({
  collection: "notes",
  filter: { title: "First note" },
  limit: 1,
}, { bearer: null });
console.log(notes);
```

The SDK returns `data` directly and throws a `SinglebaseError` for API errors. See [JavaScript SDK](/sdk-javascript.md).

## Python example

Copy the small client from [Python SDK](/sdk-python.md) into `singlebase.py`. It uses the standard library and returns the full `Result` envelope.

```python
import asyncio
import os
from singlebase import singlebaseClient, Result

async def main():
    sbc = singlebaseClient(
        api_key=os.environ["SINGLEBASE_SERVER_KEY"],
        api_url=os.environ["SINGLEBASE_API_URL"],
    )
    created: Result = await sbc.call("data.insert", {
        "collection": "notes",
        "data": {"title": "First note", "status": "active"},
    }, options={})
    if created.error:
        raise RuntimeError(created.error["message"])
    print(created.data)
    notes: Result = await sbc.call("data.query", {
        "collection": "notes", "filter": {"title": "First note"}, "limit": 1,
    }, options={})
    if notes.error:
        raise RuntimeError(notes.error["message"])
    print(notes.data)

asyncio.run(main())
```

## Give an agent access

Create an agent key in the console with `read` on `notes`. Use that key in `X-API-KEY` to make the query above from the agent's server-side tool handler. Add `write` only if it also needs to create notes.

For generation such as `llm.summarize`, an agent needs `ai.generate`. For `chat.ask` and persisted chats it needs `ai.chat`. See [Agent permissions](/agents.md#permissions).

## Connect a signed-in user

Browser and mobile clients use a web key (`wk_`) plus `Authorization: Bearer <ID_TOKEN>` for user-scoped operations. A signed-in insert automatically belongs to that user; reads and mutations are scoped to their documents. See [Authentication](/auth.md) for signup and signin, and [API overview](/api.md) for headers and errors.
