# Beezifi Workspace — API Reference

Base URL: `http://localhost:3000/api`

All authenticated endpoints require:
```
Authorization: Bearer <access_token>
```

Tenant-scoped endpoints also require:
```
X-Tenant-Slug: <org_slug>
```

---

## Authentication

### POST /auth/register
```json
{ "email": "user@example.com", "username": "johndoe", "password": "Password123!", "displayName": "John Doe" }
```
**Response 201:** `{ user }`

### POST /auth/login
```json
{ "email": "user@example.com", "password": "Password123!" }
```
**Response 200:** `{ accessToken, user }` + sets `refresh_token` cookie

### POST /auth/refresh
Uses `refresh_token` cookie.
**Response 200:** `{ accessToken }` + rotates cookie

### POST /auth/logout *(auth)*
Revokes refresh token, sets user offline.

### GET /auth/me *(auth)*
**Response 200:** `{ user }`

---

## Organizations

### GET /organizations/me *(auth)*
List all workspaces the current user belongs to.

### POST /organizations *(auth)*
```json
{ "name": "Acme Corp", "description": "..." }
```
Creates workspace + default channels + owner membership.

### GET /organizations/:slug *(auth + member)*
### PATCH /organizations/:slug *(auth + admin)*
### GET /organizations/:slug/members *(auth + member)*
### POST /organizations/:slug/members/invite *(auth + admin)*
```json
{ "email": "newuser@example.com", "role": "member" }
```
### PATCH /organizations/:slug/members/:userId/role *(auth + admin)*
```json
{ "role": "admin" }
```
### DELETE /organizations/:slug/members/:userId *(auth + admin)*

---

## Channels

Base: `/organizations/:slug/channels`

### GET / — List channels (member sees public + joined private)
### POST / — Create channel
```json
{ "name": "design", "description": "...", "type": "public|private" }
```
### POST /dm — Open DM
```json
{ "userIds": ["uuid1", "uuid2"] }
```
### GET /:channelId
### PATCH /:channelId
### DELETE /:channelId/archive
### POST /:channelId/join
### DELETE /:channelId/leave
### GET /:channelId/members
### POST /:channelId/read — Mark last read
```json
{ "messageId": "uuid" }
```

---

## Messages

### GET /:channelId/messages
Query params: `limit`, `before` (cursor), `after`

**Response:**
```json
{
  "messages": [ { "id", "content", "type", "user_id", "username", "display_name", "avatar_url", "created_at", "is_edited", "reply_count", "reactions": [ { "emoji", "count", "users": [] } ] } ],
  "hasMore": true,
  "nextCursor": "uuid"
}
```

### POST /:channelId/messages
```json
{ "content": "Hello!", "type": "text", "parentMessageId": null }
```

### PATCH /:channelId/messages/:messageId
```json
{ "content": "Updated content" }
```

### DELETE /:channelId/messages/:messageId
Soft-deletes. Channel admins can delete any message.

### POST /:channelId/messages/:messageId/reactions
```json
{ "emoji": "👍" }
```
Toggles — adds if not present, removes if already there.

### GET /:channelId/messages/:messageId/replies
Returns thread replies for a parent message.

---

## Search

### GET /organizations/:slug/search
Query: `q=keyword&channelId=uuid&limit=20`

Uses MariaDB FULLTEXT index.

---

## Files

### POST /organizations/:slug/files
`multipart/form-data` with `file`, optional `channelId`, `messageId`.

**Response 201:** `{ file: { id, original_name, mime_type, size, storage_path } }`

### GET /organizations/:slug/files/:fileId/download
Streams file with proper Content-Disposition header.

### DELETE /organizations/:slug/files/:fileId
Owner only.

### GET /organizations/:slug/channels/:channelId/files
List files shared in a channel.

---

## Notifications

### GET /organizations/:slug/notifications
Query: `limit`, `unread=true`

### GET /organizations/:slug/notifications/unread
**Response:** `{ count: 5 }`

### POST /organizations/:slug/notifications/read
```json
{ "ids": ["uuid1", "uuid2"] }
```

### POST /organizations/:slug/notifications/read-all

---

## Users

### GET /users/profile/:userId *(auth)*
### PATCH /users/profile *(auth)*
```json
{ "displayName": "Jane Doe", "statusMessage": "In a meeting", "timezone": "America/New_York" }
```
### PATCH /users/status *(auth)*
```json
{ "status": "away", "statusMessage": "BRB" }
```
### POST /users/change-password *(auth)*
```json
{ "currentPassword": "...", "newPassword": "..." }
```
### GET /organizations/:slug/users/search
Query: `q=alice`

---

## Admin — Org

### GET /organizations/:slug/admin/stats *(admin)*
```json
{ "stats": { "members", "channels", "messages", "files", "storage", "onlineNow" } }
```
### GET /organizations/:slug/admin/logs *(admin)*
Query: `limit`, `offset`, `action`, `userId`

### GET/POST/DELETE /organizations/:slug/admin/webhooks *(admin)*

---

## Admin — System

### GET /admin/organizations *(system admin)*
### PATCH /admin/organizations/:orgId/toggle *(system admin)*
### GET /admin/stats *(system admin)*

---

## WebSocket Events

Connect: `ws://localhost:3000?token=<access_token>`

### Client → Server

| Type | Payload |
|------|---------|
| `channel.join` | `{ channelId }` |
| `channel.leave` | `{ channelId }` |
| `message.send` | `{ channelId, content, parentMessageId? }` |
| `message.typing` | `{ channelId, isTyping }` |
| `presence.update` | `{ status }` |
| `ping` | `{}` |

### Server → Client

| Type | Payload |
|------|---------|
| `connected` | `{ user }` |
| `message.new` | `{ message }` |
| `thread.message` | `{ message }` |
| `channel.new` | `{ channel }` |
| `channel.updated` | `{ channel }` |
| `channel.archived` | `{ channelId }` |
| `user.typing` | `{ channelId, user, isTyping }` |
| `presence.join` | `{ channelId, user }` |
| `presence.leave` | `{ channelId, userId }` |
| `presence.update` | `{ userId, status }` |
| `notification.new` | `{ notification }` |
| `pong` | `{}` |
| `error` | `{ message }` |

---

## Webhooks (Integrations)

Register an outbound webhook on your workspace:

```bash
POST /api/organizations/acme/admin/webhooks
{
  "name": "GitHub CI",
  "url": "https://your-server.com/hook",
  "secret": "optional_hmac_secret",
  "events": ["message.created"]
}
```

Beezifi signs each delivery with `X-Beezifi-Signature: sha256=<hmac>`.

**Supported events:** `message.created`

To send a message from an external system (GitHub, PagerDuty, etc.), POST to this endpoint and the payload will appear as a message in the configured channel.
