Skip to main content

HTTP API Reference

PlugPort exposes a RESTful HTTP API on port 8080 (configurable via HTTP_PORT).

Authentication

PlugPort supports three authentication methods, evaluated in priority order:

Sign-In with Ethereum (EIP-4361) provides wallet-based authentication via encrypted cookies.

Flow:

1. POST /api/v1/auth/nonce → { nonce: "abc123" } (stores nonce in session)
2. Sign the SIWE message with your wallet (client-side)
3. POST /api/v1/auth/verify → { address: "0x...", ok: 1 } (sets session cookie + CSRF cookie)
4. All subsequent requests automatically include the session cookie

The session is encrypted using iron-session (AES-256) and stored as an httpOnly cookie. Sessions expire after 24 hours.

2. Wallet-Linked API Key

Generate API keys from the dashboard. Keys are prefixed pp_live_ (production) or pp_test_ (testing):

curl -H "Authorization: Bearer pp_live_your-key-here" http://localhost:8080/api/v1/collections

3. Legacy Static Key

Set the API_KEY environment variable and pass it via header:

API_KEY=your-secret-key pnpm --filter @plugport/server dev
curl -H "x-api-key: your-secret-key" http://localhost:8080/api/v1/collections

CSRF Protection

Session-authenticated (SIWE) mutations require a CSRF token. After /auth/verify, the server sets a plugport_csrf cookie (readable by JS). Include it on all POST/PUT/DELETE requests:

curl -X POST \
-H "Cookie: plugport_session=..." \
-H "x-csrf-token: <value-from-plugport_csrf-cookie>" \
http://localhost:8080/api/v1/collections/users/insertOne \
-d '{"document": {"name": "Alice"}}'

API key and legacy key auth do not require CSRF tokens.

Rate Limits

Auth endpoints have stricter per-route rate limits:

EndpointRate Limit
POST /api/v1/auth/nonce10/min per IP
POST /api/v1/auth/verify5/min per IP
GET /api/v1/auth/me30/min per IP
POST /api/v1/auth/logout10/min per IP
All other endpoints100 / 10s per IP

Auth Endpoints

POST /api/v1/auth/nonce

Request a nonce for SIWE message signing.

Request:

{ "address": "0x1234..." }

Response:

{ "nonce": "abc123def456", "ok": 1 }

POST /api/v1/auth/verify

Verify a signed SIWE message and establish a session.

Request:

{
"message": "plugport.wtf wants you to sign in...",
"signature": "0xabc..."
}

Response:

{ "address": "0x1234...", "ok": 1 }

Sets plugport_session (httpOnly) and plugport_csrf (JS-readable) cookies.

GET /api/v1/auth/me

Check current session status.

Response (authenticated):

{ "address": "0x1234...", "chainId": 10143, "ok": 1 }

Response (not authenticated): 401

POST /api/v1/auth/logout

Destroy the current session and clear cookies.

Response:

{ "ok": 1 }

System Endpoints

GET /health

Returns server health status.

Response:

{
"status": "ok",
"uptime": 12345,
"version": "1.0.0",
"storage": {
"type": "InMemory",
"connected": true,
"keyCount": 1234
},
"server": {
"httpPort": 8080,
"wirePort": 27017
}
}

GET /metrics

Prometheus-format metrics for scraping.

plugport_requests_total{command="find"} 42
plugport_request_duration_ms{quantile="0.95"} 12.5
plugport_errors_total{code="11000"} 3

GET /api/v1/metrics

JSON metrics snapshot for dashboard integration.

Response:

{
"requests": {
"total": 1234,
"byCommand": { "find": 500, "insert": 300, "update": 200 },
"byProtocol": { "http": 1000, "wire": 234 }
},
"latency": { "p50": 2.1, "p95": 12.5, "p99": 45.0, "avg": 5.3 },
"errors": { "total": 12, "byCode": { "11000": 5 } },
"storage": { "keyCount": 5678, "estimatedSizeBytes": 123456 },
"uptime": 3600,
"timestamp": 1708300000000
}

Collection Management

GET /api/v1/collections

List all collections with stats.

Response:

{
"collections": [
{ "name": "users", "documentCount": 100, "indexCount": 3, "createdAt": 1708300000, "ownerAddress": "0x123...", "mode": "public" }
],
"ok": 1
}

POST /api/v1/collections/:name/drop

Drop a collection and all its data.

Response:

{ "acknowledged": true, "dropped": true }

GET /api/v1/collections/:name/stats

Get collection statistics.

Response:

{
"documentCount": 100,
"indexCount": 3,
"storageSizeBytes": 45678,
"indexes": [
{ "name": "_id_", "field": "_id", "unique": true }
]
}

GET /api/v1/collections/:name/privacy

Get privacy settings for a collection. Owners see the full object; non-owners see only mode and ownerAddress.

Response (owner):

{
"ok": 1,
"privacy": {
"mode": "private",
"ownerAddress": "0x123...",
"accessRoles": { "0xabc...": 1 },
"contractAddress": "0xdef..."
}
}

Response (non-owner):

{
"ok": 1,
"privacy": {
"mode": "private",
"ownerAddress": "0x123..."
}
}

POST /api/v1/collections/:name/privacy

Set privacy mode for a collection. Requires authentication. Only the collection owner (or first-time setter) can change the mode.

Request:

{ "mode": "private" }

Response:

{ "ok": 1, "collection": "users", "mode": "private" }

GET /api/v1/collections/:name/roles

View access roles for a collection. Requires authentication. Owner only — returns 403 for non-owners.

Response:

{ "accessRoles": { "0xabc...": 1, "0xdef...": 2 }, "ok": 1 }

Role values: 1 = read, 2 = write.

POST /api/v1/collections/:name/roles

Grant or revoke access roles. Requires authentication. Owner only.

Request (grant):

{ "address": "0xabc...", "action": "grant", "role": 1 }

Request (revoke):

{ "address": "0xabc...", "action": "revoke" }

Response:

{ "accessRoles": { "0xabc...": 1 }, "ok": 1 }

GET /api/v1/whitelist

Get the global address whitelist (public, no auth required).

Response:

{ "addresses": ["0xabc...", "0xdef..."], "ok": 1 }

POST /api/v1/whitelist

Add or remove an address from the global whitelist. Requires authentication.

Request:

{ "address": "0xabc...", "action": "add" }

Response:

{ "ok": 1, "addresses": ["0xabc..."] }

Document Operations

POST /api/v1/collections/:name/insertOne

Insert a single document.

Request:

{
"document": {
"name": "Alice",
"email": "alice@example.com",
"age": 30
}
}

Response:

{
"acknowledged": true,
"insertedId": "67b2a1f0a1b2c3d4e5f6a7b8",
"insertedCount": 1
}

POST /api/v1/collections/:name/insertMany

Insert multiple documents.

Request:

{
"documents": [
{ "name": "Alice", "age": 30 },
{ "name": "Bob", "age": 25 }
]
}

Response:

{
"acknowledged": true,
"insertedCount": 2,
"insertedIds": ["67b2a1f0...", "67b2a1f1..."]
}

POST /api/v1/collections/:name/find

Query documents with filters, sorting, projection, and pagination.

Request:

{
"filter": { "age": { "$gte": 25 } },
"projection": { "name": 1, "age": 1 },
"sort": { "age": -1 },
"limit": 10,
"skip": 0
}

Response:

{
"cursor": {
"firstBatch": [
{ "_id": "67b2a1f0...", "name": "Alice", "age": 30 },
{ "_id": "67b2a1f1...", "name": "Bob", "age": 25 }
],
"id": 0
},
"ok": 1
}

POST /api/v1/collections/:name/findOne

Find a single document.

Request:

{
"filter": { "email": "alice@example.com" }
}

Response:

{
"document": { "_id": "67b2a1f0...", "name": "Alice", "email": "alice@example.com" }
}

Returns { "document": null } if no match found.

POST /api/v1/collections/:name/updateOne

Update a single document.

Request:

{
"filter": { "_id": "67b2a1f0..." },
"update": { "$set": { "age": 31, "updatedAt": "2024-01-01T00:00:00Z" } },
"upsert": false
}

Response:

{
"acknowledged": true,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedId": null
}

POST /api/v1/collections/:name/updateMany

Update all documents matching the filter.

Request:

{
"filter": { "status": "inactive" },
"update": { "$set": { "archived": true } }
}

Response:

{
"acknowledged": true,
"matchedCount": 5,
"modifiedCount": 5,
"upsertedId": null
}

POST /api/v1/collections/:name/deleteOne

Delete a single document.

Request:

{ "filter": { "_id": "67b2a1f0..." } }

Response:

{ "acknowledged": true, "deletedCount": 1 }

POST /api/v1/collections/:name/deleteMany

Delete all documents matching the filter.

Request:

{ "filter": { "status": "inactive" } }

Response:

{ "acknowledged": true, "deletedCount": 5 }

Index Operations

POST /api/v1/collections/:name/createIndex

Create an index on a field.

Request:

{ "field": "email", "unique": true }

Response:

{ "acknowledged": true, "indexName": "email_1" }

GET /api/v1/collections/:name/indexes

List all indexes on the collection.

Response:

{
"indexes": [
{ "name": "_id_", "field": "_id", "unique": true },
{ "name": "email_1", "field": "email", "unique": true }
]
}

POST /api/v1/collections/:name/dropIndex

Drop an index by name.

Request:

{ "name": "email_1" }

Response:

{ "acknowledged": true }

POST /api/v1/collections/:name/count

Count documents matching a filter.

Request:

{ "filter": { "status": "active" } }

Response:

{ "count": 42, "ok": 1 }

POST /api/v1/collections/:name/distinct

Get distinct values of a field across documents.

Request:

{ "field": "category", "filter": {} }

Response:

{ "values": ["electronics", "clothing", "books"], "ok": 1 }

POST /api/v1/collections/:name/aggregate

Execute an aggregation pipeline on a collection.

Supported Stages: $match, $lookup, $project, $sort, $limit, $skip, $unwind, $count.

Request:

{
"pipeline": [
{ "$match": { "status": "completed" } },
{ "$lookup": {
"from": "users",
"localField": "userId",
"foreignField": "_id",
"as": "user"
}},
{ "$unwind": "$user" },
{ "$project": { "orderId": 1, "total": 1, "user.name": 1 } },
{ "$sort": { "total": -1 } },
{ "$limit": 10 }
]
}

Response:

{
"cursor": {
"firstBatch": [
{ "_id": "abc123", "orderId": "ORD-001", "total": 99.99, "user": { "name": "Alice" } }
],
"id": 0,
"ns": "plugport.orders"
},
"ok": 1
}

Multi-Protocol Endpoints

PlugPort supports SQL and Redis command interfaces that translate to the underlying document store.

GET /api/v1/protocols

List all protocol frontends and their status.

Response:

{
"protocols": [
{ "name": "mongodb", "enabled": true, "port": 27017 },
{ "name": "postgresql", "enabled": false },
{ "name": "redis", "enabled": true, "port": 6379 }
],
"ok": 1
}

POST /api/v1/sql

Execute a SQL query translated to PlugPort document operations. Supports SELECT, INSERT, UPDATE, DELETE, CREATE INDEX, and DROP INDEX.

Request:

{ "query": "SELECT name, age FROM users WHERE age >= 25 ORDER BY age DESC LIMIT 10" }

Response:

{
"ok": 1,
"result": {
"cursor": {
"firstBatch": [
{ "_id": "...", "name": "Alice", "age": 30 }
],
"id": 0
},
"ok": 1
}
}

POST /api/v1/redis

Execute a Redis command via the RESP protocol translation layer.

Request:

{ "command": ["SET", "mykey", "myvalue"] }

Response:

{ "ok": 1, "result": "OK" }

Supported commands: GET, SET, DEL, MGET, MSET, INCR, DECR, APPEND, STRLEN, SETNX, EXISTS, RENAME, KEYS, TYPE, TTL, PTTL, PERSIST, EXPIRE, PEXPIRE, HSET, HGET, HGETALL, HDEL, HKEYS, HVALS, HEXISTS, HLEN, HMSET, HMGET, LPUSH, RPUSH, LPOP, RPOP, LLEN, LRANGE, SADD, SREM, SMEMBERS, SISMEMBER, SCARD, SUBSCRIBE, PUBLISH, UNSUBSCRIBE, PSUBSCRIBE, PING, INFO, DBSIZE, FLUSHDB, SELECT, AUTH, COMMAND.

GET /api/v1/redis/stream

Server-Sent Events (SSE) stream for Redis Pub/Sub messages.

Query Parameters:

  • channels (required): Comma-separated list of channels to subscribe to.

Example:

curl -N "http://localhost:8080/api/v1/redis/stream?channels=chat,notifications"

SSE Events:

data: {"channel": "chat", "message": "Hello world"}

data: {"event": "subscribe", "channel": "chat", "count": 1}

POST /api/v1/protocols/:name/enable

Enable a protocol frontend. Requires authentication.

Response:

{ "ok": 1, "protocol": "postgresql", "enabled": true }

POST /api/v1/protocols/:name/disable

Disable a protocol frontend. Requires authentication.

Response:

{ "ok": 1, "protocol": "postgresql", "enabled": false }

API Key Management

All API key endpoints require session authentication (SIWE). Keys are scoped to the authenticated wallet.

POST /api/v1/keys/generate

Generate a new wallet-linked API key.

Request:

{
"label": "Production Backend",
"permissions": ["read", "write"],
"rateLimit": 100
}

Response:

{
"apiKey": "pp_live_abc123...",
"hash": "sha256_hash",
"metadata": {
"label": "Production Backend",
"permissions": ["read", "write"],
"rateLimit": 100,
"createdAt": 1720000000000
},
"ok": 1
}

Important: The full API key is only shown once. Store it securely.

GET /api/v1/keys

List all API keys for the authenticated user.

Response:

{
"keys": [
{
"hash": "sha256_hash",
"label": "Production Backend",
"prefix": "pp_live_abc...",
"permissions": ["read", "write"],
"createdAt": 1720000000000
}
],
"ok": 1
}

DELETE /api/v1/keys/:hash

Revoke an API key. Returns 404 if key not found or not owned by you.

Response:

{ "ok": 1, "revoked": true }

POST /api/v1/keys/:hash/rotate

Rotate an API key — generates a new key value with the same metadata.

Response:

{
"apiKey": "pp_live_newkey...",
"hash": "new_sha256_hash",
"metadata": { "label": "Production Backend", "permissions": ["read", "write"] },
"ok": 1
}

PUT /api/v1/keys/:hash/permissions

Update the permissions of an API key.

Request:

{ "permissions": ["read"] }

Response:

{ "ok": 1, "updated": true }

Permission values: "all", "read", "write", "admin".

GET /api/v1/keys/:hash/analytics

Get usage analytics for a specific API key.

Query Parameters:

  • days (optional, default: 7): Number of days to include.

Response:

{
"analytics": {
"totalRequests": 1234,
"byEndpoint": { "/api/v1/collections/users/find": 500 },
"byDay": [{ "date": "2026-07-10", "count": 200 }]
},
"ok": 1
}

GET /api/v1/analytics/overview

Get aggregated analytics across all your API keys.

Response:

{
"overview": {
"totalRequests": 5000,
"byKey": [
{ "hash": "sha256_hash", "label": "Production Backend", "count": 3000 }
]
},
"ok": 1
}

Error Responses

All errors follow MongoDB-compatible error codes:

{
"ok": 0,
"code": 11000,
"codeName": "DuplicateKey",
"errmsg": "Duplicate key error: field 'email' value 'alice@example.com'"
}
HTTP StatusError CodeDescription
4002Bad value / invalid request
40113Unauthorized (missing API key)
40426Namespace/collection not found
40911000Duplicate key violation
41310334Document too large
5001Internal server error