---
name: turabase
version: 2.0.0
description: Backend-as-a-Service. Projects, schema-first collections, file storage, and auth rules — all over REST or the turabase-js SDK. No servers, no connection strings.
homepage: https://www.turabase.com
metadata: {"category":"database","api_base":"https://www.turabase.com/api/v1"}
---

# TuraBase

**Backend-as-a-Service for developers who want to ship, not manage infrastructure.**

TuraBase gives you a complete backend in one API key: structured data storage (collections), file storage (buckets), per-collection access rules, and auth via TuraLogin. Each project auto-provisions a managed Postgres instance and object storage bucket from TuraDB under the hood — you never touch connection strings or credentials.

Think of TuraBase like Firebase or Supabase, but built on Tura's infrastructure stack.

## Skill Files

| File | URL |
|------|-----|
| **SKILL.md** (this file) | `https://www.turabase.com/SKILL.md` |
| **AGENTS.md** | `https://www.turabase.com/AGENTS.md` |
| **API.md** | `https://www.turabase.com/API.md` |
| **QUICKSTART.md** | `https://www.turabase.com/QUICKSTART.md` |
| **skill.json** (metadata) | `https://www.turabase.com/skill.json` |

**Base URL:** `https://www.turabase.com/api/v1`

**Authentication:** `Authorization: Bearer <API_KEY>` — Use TuraLogin API keys from https://www.turalogin.com/dashboard/keys

---

## How It Works

```
Your app
  │
  ├── turabase-js SDK  (or plain REST)
  │
  └── TuraBase API
        ├── Projects ────── auto-provisions TuraDB Postgres + object storage
        ├── Collections ─── schema-first structured data (Postgres, no SQL needed)
        ├── Files ────────── upload/download (object storage, signed URLs)
        └── Auth rules ───── per-collection access via TuraLogin sessions
```

One TuraLogin API key covers everything. The same key that authenticates your users writes and reads their data.

---

## Quick Start

### 1. Get Your API Key

Get one at https://www.turalogin.com/dashboard/keys

- Test: `tl_test_xxxxxxxxxxxxxxxx`
- Production: `tl_live_xxxxxxxxxxxxxxxx`

### 2. Install the SDK (optional but recommended)

```bash
npm install turabase-js
```

### 3. Create a Project

```bash
curl -X POST https://www.turabase.com/api/v1/projects \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "my-app" }'
```

**Response:**
```json
{
  "id": "proj_abc123",
  "name": "my-app",
  "status": "active",
  "createdAt": "2026-02-22T10:00:00Z"
}
```

### 4. Store a Record

```bash
curl -X POST https://www.turabase.com/api/v1/projects/proj_abc123/collections/posts/records \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "title": "Hello TuraBase",
      "authorId": "user_xyz",
      "published": true
    }
  }'
```

### 5. Query Records

```bash
curl "https://www.turabase.com/api/v1/projects/proj_abc123/collections/posts/records?published=true&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

---

## SDK Usage (turabase-js)

```typescript
import { createClient } from 'turabase-js'

const db = createClient({
  project: 'proj_abc123',
  apiKey: 'tl_live_xxx',
})

// --- Structured data ---

// Insert a record
await db.from('posts').insert({
  title: 'Hello TuraBase',
  authorId: 'user_xyz',
  published: true,
})

// Query with filters
const { data, nextCursor } = await db
  .from('posts')
  .select()
  .eq('published', true)
  .limit(20)

// Get one record
const { data: post } = await db.from('posts').select().id('rec_abc123')

// Update a record
await db.from('posts').update('rec_abc123', { title: 'Updated title' })

// Delete a record
await db.from('posts').delete('rec_abc123')

// --- File storage ---

// Upload a file
const { file } = await db.storage.from('avatars').upload('alice.png', fileBlob)

// Get a signed URL (expires in 3600 seconds)
const { url } = await db.storage.from('avatars').getSignedUrl('alice.png', 3600)

// Get a public URL (only works if the collection is public)
const { url } = await db.storage.from('avatars').getPublicUrl('alice.png')

// List files
const { files } = await db.storage.from('avatars').list({ folder: 'profile-pics' })

// Delete a file
await db.storage.from('avatars').remove('alice.png')
```

---

## Full API Reference

### Projects

A project is the top-level container. Creating a project automatically provisions a managed Postgres instance (for collections) and an object storage bucket (for files) from TuraDB.

#### POST /api/v1/projects

Create a new project.

**Request Body:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Project name (alphanumeric, hyphens) |
| `region` | string | No | `us-east-1`, `eu-west-1` (default: `us-east-1`) |

**Success Response (201):**
```json
{
  "id": "proj_abc123",
  "name": "my-app",
  "region": "us-east-1",
  "status": "active",
  "createdAt": "2026-02-22T10:00:00Z"
}
```

---

#### GET /api/v1/projects

List all projects.

**Success Response (200):**
```json
{
  "projects": [
    {
      "id": "proj_abc123",
      "name": "my-app",
      "region": "us-east-1",
      "status": "active",
      "createdAt": "2026-02-22T10:00:00Z"
    }
  ]
}
```

---

#### GET /api/v1/projects/:id

Get project details.

**Success Response (200):**
```json
{
  "id": "proj_abc123",
  "name": "my-app",
  "region": "us-east-1",
  "status": "active",
  "usage": {
    "storageUsedMb": 512,
    "recordCount": 14200,
    "fileCount": 830
  },
  "createdAt": "2026-02-22T10:00:00Z"
}
```

---

#### DELETE /api/v1/projects/:id

Delete a project and all data inside it (irreversible).

---

### Collections (structured data)

Collections hold schema-validated records, backed by Postgres. You do not write SQL. Define a schema once; every write is validated automatically.

#### POST /api/v1/projects/:id/collections

Create or update a collection schema. Re-posting an existing collection name updates the schema (additive — columns are never dropped automatically).

**Request Body:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Collection name (alphanumeric, underscores) |
| `schema` | object | Yes | Field definitions |
| `access` | string | No | `public`, `authenticated`, `owner` (default: `authenticated`) |

**Schema field types:** `string`, `number`, `boolean`, `date`, `json`

**Example:**
```json
{
  "name": "posts",
  "schema": {
    "title": { "type": "string", "required": true },
    "body": { "type": "string" },
    "authorId": { "type": "string", "required": true },
    "published": { "type": "boolean", "default": false },
    "publishedAt": { "type": "date" }
  },
  "access": "owner"
}
```

**Success Response (201):**
```json
{
  "name": "posts",
  "access": "owner",
  "schema": { ... },
  "createdAt": "2026-02-22T10:00:00Z"
}
```

---

#### GET /api/v1/projects/:id/collections

List all collections in a project.

---

#### POST /api/v1/projects/:id/collections/:name/records

Create a record.

**Request Body:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `data` | object | Yes | Record fields (validated against collection schema) |

**Success Response (201):**
```json
{
  "id": "rec_abc123xyz",
  "collection": "posts",
  "data": {
    "title": "Hello TuraBase",
    "authorId": "user_xyz",
    "published": false
  },
  "createdAt": "2026-02-22T10:00:00Z",
  "updatedAt": "2026-02-22T10:00:00Z"
}
```

---

#### GET /api/v1/projects/:id/collections/:name/records

Query records with filters.

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `[field]` | string | Filter by any schema field (e.g. `published=true`) |
| `limit` | number | Max results (default: 20, max: 100) |
| `cursor` | string | Cursor for next page |
| `orderBy` | string | Field to sort by |
| `order` | string | `asc` or `desc` (default: `desc`) |

**Success Response (200):**
```json
{
  "records": [
    {
      "id": "rec_abc123",
      "collection": "posts",
      "data": { "title": "Hello TuraBase", "published": true },
      "createdAt": "2026-02-22T10:00:00Z"
    }
  ],
  "nextCursor": "eyJpZCI6InJlY19hYmMxMjMifQ==",
  "hasMore": true
}
```

---

#### GET /api/v1/projects/:id/records/:recordId

Get a single record by ID.

---

#### PATCH /api/v1/projects/:id/records/:recordId

Partial update — only the fields you send are changed.

```json
{ "data": { "published": true, "publishedAt": "2026-02-22T10:00:00Z" } }
```

---

#### DELETE /api/v1/projects/:id/records/:recordId

Delete a record.

---

### Files (unstructured data)

File storage is backed by TuraDB object storage. Each project has one storage bucket. Files are organized into folders.

#### POST /api/v1/projects/:id/files

Upload a file (multipart form data).

**Form Fields:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `file` | File | Yes | The file to upload |
| `folder` | string | No | Virtual folder path (e.g. `avatars/2026`) |
| `name` | string | No | Override the file name |

**Success Response (201):**
```json
{
  "id": "file_abc123",
  "name": "alice.png",
  "folder": "avatars",
  "mimeType": "image/png",
  "sizeBytes": 84210,
  "url": "https://proj-abc123.storage.turadb.com/avatars/alice.png",
  "createdAt": "2026-02-22T10:00:00Z"
}
```

---

#### GET /api/v1/projects/:id/files

List files.

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `folder` | string | Filter by folder prefix |
| `mimeType` | string | Filter by MIME type (e.g. `image/png`) |
| `limit` | number | Max results (default: 20, max: 100) |
| `cursor` | string | Pagination cursor |

---

#### GET /api/v1/projects/:id/files/:fileId

Get file metadata.

**Success Response (200):**
```json
{
  "id": "file_abc123",
  "name": "alice.png",
  "folder": "avatars",
  "mimeType": "image/png",
  "sizeBytes": 84210,
  "url": "https://proj-abc123.storage.turadb.com/avatars/alice.png",
  "createdAt": "2026-02-22T10:00:00Z"
}
```

---

#### GET /api/v1/projects/:id/files/:fileId/signed-url

Get a time-limited signed URL for a private file.

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `expiresIn` | number | Seconds until the URL expires (default: 3600, max: 86400) |

**Success Response (200):**
```json
{
  "url": "https://proj-abc123.storage.turadb.com/avatars/alice.png?sig=xxx&exp=1740218400",
  "expiresAt": "2026-02-22T11:00:00Z"
}
```

---

#### DELETE /api/v1/projects/:id/files/:fileId

Delete a file.

---

### Access Rules

Access rules control who can read and write each collection. They apply to both the REST API and the SDK.

| Rule | Who can read/write |
|------|--------------------|
| `public` | Anyone, no API key needed |
| `authenticated` | Requests with a valid TuraLogin session (default) |
| `owner` | Only the user whose `userId` matches the record's `authorId` field |

Set access when creating or updating a collection schema:

```json
{
  "name": "posts",
  "schema": { ... },
  "access": "owner"
}
```

**How `owner` access works:**

When you write a record under `owner` access, TuraBase automatically sets an `_ownerId` field from the TuraLogin session. On reads and writes, TuraBase filters to records where `_ownerId` matches the requester's user ID.

To pass the user's identity, include the session token:

```bash
curl -X POST https://www.turabase.com/api/v1/projects/proj_abc123/collections/posts/records \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Turabase-Session: USER_SESSION_JWT" \
  -H "Content-Type: application/json" \
  -d '{ "data": { "title": "My post" } }'
```

In the SDK, pass the session on the client:

```typescript
const db = createClient({
  project: 'proj_abc123',
  apiKey: 'tl_live_xxx',
  session: userSessionJwt,  // from TuraLogin verify response
})
```

---

## Framework Examples

### Next.js (App Router)

```typescript
// lib/turabase.ts — server-side client
import { createClient } from 'turabase-js'

export const db = createClient({
  project: process.env.TURABASE_PROJECT_ID!,
  apiKey: process.env.TURABASE_API_KEY!,
})
```

```typescript
// app/api/posts/route.ts — create a post
import { db } from '@/lib/turabase'
import { cookies } from 'next/headers'

export async function POST(request: Request) {
  const session = cookies().get('session')?.value
  const { title, body } = await request.json()

  const { data, error } = await db
    .withSession(session)  // pass TuraLogin session for owner-scoped writes
    .from('posts')
    .insert({ title, body })

  if (error) return Response.json({ error }, { status: 400 })
  return Response.json(data, { status: 201 })
}

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)

  const { data } = await db
    .from('posts')
    .select()
    .eq('published', true)
    .limit(Number(searchParams.get('limit') ?? 20))

  return Response.json(data)
}
```

```typescript
// app/api/avatar/route.ts — upload an avatar
import { db } from '@/lib/turabase'

export async function POST(request: Request) {
  const formData = await request.formData()
  const file = formData.get('file') as File

  const { file: uploaded, error } = await db.storage
    .from('avatars')
    .upload(`${Date.now()}-${file.name}`, file)

  if (error) return Response.json({ error }, { status: 400 })
  return Response.json({ url: uploaded.url }, { status: 201 })
}
```

### Express.js

```javascript
const { createClient } = require('turabase-js')

const db = createClient({
  project: process.env.TURABASE_PROJECT_ID,
  apiKey: process.env.TURABASE_API_KEY,
})

// Create post
router.post('/posts', async (req, res) => {
  const session = req.cookies.session
  const { title, body } = req.body

  const { data, error } = await db
    .withSession(session)
    .from('posts')
    .insert({ title, body })

  if (error) return res.status(400).json({ error })
  res.status(201).json(data)
})

// List posts
router.get('/posts', async (req, res) => {
  const { data } = await db.from('posts').select().eq('published', true)
  res.json(data)
})
```

---

## Environment Variables

```bash
TURABASE_PROJECT_ID=proj_abc123
TURABASE_API_KEY=tl_live_xxxxxxxxxxxxxxxx
```

---

## Error Codes

| Status | Error | Description |
|--------|-------|-------------|
| 400 | `name is required` | Missing project or collection name |
| 400 | `data is required` | Missing record data |
| 400 | `Schema validation failed` | Record fields don't match collection schema |
| 401 | `Unauthorized` | Missing or invalid API key |
| 403 | `Access denied` | Session does not satisfy collection access rule |
| 404 | `Not found` | Project, collection, record, or file not found |
| 409 | `Name already in use` | Project name taken |
| 429 | `Too many requests` | Rate limit exceeded |

---

## Support & Resources

- 🌐 Website: https://www.turabase.com
- 🔑 API Keys: https://www.turalogin.com/dashboard/keys
- 🔐 Auth: https://www.turalogin.com/SKILL.md
- 🏗️ Infrastructure: https://www.turadb.com/SKILL.md
