Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 3 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,57 +115,13 @@ npm run cli -- internal-doc.jpg \

---

### 🐳 Docker API Examples
The Docker API runs on port 3000 by default. It uses standard detection settings (Emails, IPs, Keys, PII) by default, but is **fully configurable** via the `settings` parameter.

The Docker API runs on port 3000 by default. It currently uses standard detection settings (Emails, IPs, Keys, PII).
👉 **[View Full API Documentation](docs/API.md)** for detailed usage, schema, and Python/Node.js examples.

#### 1. Quick Test via Curl
#### Quick Test (Curl)
```bash
curl -X POST http://localhost:3000/redact \
-F "image=@/path/to/doc.jpg" \
-o redacted.png
```
*Check the `X-Redacted-Stats` header in the response for detection counts.*

#### 2. Python Integration
Process images programmatically in your Python apps:

```python
import requests

url = 'http://localhost:3000/redact'
files = {'image': open('contract.jpg', 'rb')}

response = requests.post(url, files=files)

if response.status_code == 200:
with open('redacted_contract.png', 'wb') as f:
f.write(response.content)
print("Stats:", response.headers.get('X-Redacted-Stats'))
else:
print("Error:", response.text)
```

#### 3. Node.js Integration (Native Fetch)
Requires Node.js 18+. No extra libraries needed!

```javascript
import fs from 'fs';

const fileBuffer = fs.readFileSync('id_card.jpg');
const blob = new Blob([fileBuffer], { type: 'image/jpeg' });

const formData = new FormData();
formData.append('image', blob, 'id_card.jpg');

const response = await fetch('http://localhost:3000/redact', {
method: 'POST',
body: formData
});

if (response.ok) {
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync('redacted_id.png', buffer);
console.log('Stats:', response.headers.get('x-redacted-stats'));
}
```
133 changes: 133 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# AutoRedact API Documentation

The AutoRedact API provides a simple, high-performance interface for redacting sensitive information from images using the same engine as the CLI and Web UI.

## Base URL

By default, the Docker container exposes the API on port `3000`.

```
http://localhost:3000
```

---

## Endpoints

### 1. Redact Image

**POST** \`/redact\`

Uploads an image for processing and returns the redacted image.

#### Request Headers
* **Content-Type**: \`multipart/form-data\`

#### Request Body (Multipart Fields)

| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| \`image\` | File | **Yes** | The image file to process. Supports \`.jpg\` and \`.png\`. |
| \`settings\` | JSON String | No | Configuration object to control detection rules. |

#### Settings Schema

The \`settings\` field accepts a JSON string matching the \`DetectionSettings\` interface:

```json
{
"email": boolean, // Default: true
"ip": boolean, // Default: true
"creditCard": boolean, // Default: true
"secret": boolean, // Default: true
"pii": boolean, // Default: true
"allowlist": string[], // e.g. ["127.0.0.1", "MyCorp"]
"blockWords": string[], // e.g. ["CONFIDENTIAL"]
"customRegex": [
{
"pattern": "regex_pattern",
"flags": "i" // Optional
}
]
}
```

#### Response

* **Status**: \`200 OK\`
* **Content-Type**: \`image/png\` (The redacted image)
* **Header** \`X-Redacted-Stats\`: JSON string containing detection counts.

---

## Examples

### 1. Basic Usage (Curl)
Redact an image using default settings:

```bash
curl -X POST http://localhost:3000/redact \\
-F "image=@/path/to/document.jpg" \\
-o redacted.png
```

### 2. Advanced Configuration (Curl)
Disable email redaction and add custom block words:

```bash
curl -X POST http://localhost:3000/redact \\
-F "image=@invoice.jpg" \\
-F 'settings={"email":false, "blockWords":["CONFIDENTIAL", "SSN"]}' \\
-o redacted_invoice.png
```

### 3. Node.js (Native Fetch)
Requires Node 18+:

```javascript
import fs from 'fs';

const fileBuffer = fs.readFileSync('doc.jpg');
const blob = new Blob([fileBuffer], { type: 'image/jpeg' });

const formData = new FormData();
formData.append('image', blob, 'doc.jpg');
formData.append('settings', JSON.stringify({
allowlist: ['CorpName'],
ip: false // Disable IP scanner
}));

const response = await fetch('http://localhost:3000/redact', {
method: 'POST',
body: formData
});

if (response.ok) {
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync('redacted.png', buffer);
console.log('Stats:', response.headers.get('x-redacted-stats'));
}
```

### 4. Python (Requests)

```python
import requests
import json

url = 'http://localhost:3000/redact'
files = {'image': open('doc.jpg', 'rb')}
settings = {
"email": False,
"blockWords": ["DO NOT DISTRIBUTE"]
}

data = {'settings': json.dumps(settings)}

response = requests.post(url, files=files, data=data)

if response.status_code == 200:
with open('redacted.png', 'wb') as f:
f.write(response.content)
print("Stats:", response.headers.get('X-Redacted-Stats'))
```
76 changes: 42 additions & 34 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { processImage } from './core/processor';
import { NodeCanvasAdapter } from './adapters/NodeCanvasAdapter';
import type { DetectionSettings } from './types';

import { DEFAULT_ALLOWLIST } from './constants/config';

const server = fastify({ logger: true });

// Register multipart support for file uploads
Expand All @@ -21,40 +23,44 @@ server.get('/health', async () => {
});

server.post('/redact', async (req, reply) => {
const data = await req.file();

if (!data) {
return reply.code(400).send({ error: 'No image file uploaded' });
}

if (!['image/jpeg', 'image/png'].includes(data.mimetype)) {
return reply.code(400).send({ error: 'Only .jpg and .png files are supported' });
}
const parts = req.parts();
let imageBuffer: Buffer | undefined;
let settings: DetectionSettings = {
email: true,
ip: true,
creditCard: true,
secret: true,
pii: true,
allowlist: [...DEFAULT_ALLOWLIST],
blockWords: [],
customDates: [],
customRegex: []
};

try {
const buffer = await data.toBuffer();

// Parse settings (optional)
// Note: multipart fields come as streams or values.
// For simplicity, we can inspect other fields if parsed,
// but @fastify/multipart with req.file() iterates.
// Let's attach settings via a specific header or query param for mvp,
// or parse fields if needed.
// Better: simple default settings for now, allow enhancement later.
const settings: DetectionSettings = {
email: true,
ip: true,
creditCard: true,
secret: true,
pii: true,
// Default empty limits
allowlist: [],
blockWords: [],
customDates: [],
customRegex: []
};

const result = await processImage(buffer, {
for await (const part of parts) {
if (part.type === 'file' && part.fieldname === 'image') {
if (!['image/jpeg', 'image/png'].includes(part.mimetype)) {
// We continue to consume the stream to avoid hanging, but note the error
// Or just throw immediately if we want to fail fast
throw new Error('Only .jpg and .png files are supported');
}
imageBuffer = await part.toBuffer();
} else if (part.type === 'field' && part.fieldname === 'settings') {
try {
const parsed = JSON.parse(part.value as string);
settings = { ...settings, ...parsed };
} catch {
req.log.warn('Failed to parse settings JSON');
}
}
}

if (!imageBuffer) {
return reply.code(400).send({ error: 'No image file uploaded' });
}

const result = await processImage(imageBuffer, {
canvasFactory: adapter,
detectionSettings: settings,
});
Expand All @@ -70,9 +76,11 @@ server.post('/redact', async (req, reply) => {
reply.type('image/png');
return outputBuffer;

} catch (err) {
} catch (err: unknown) {
req.log.error(err);
return reply.code(500).send({ error: 'Redaction processing failed' });
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
const status = errorMessage.includes('supported') ? 400 : 500;
return reply.code(status).send({ error: errorMessage || 'Redaction processing failed' });
}
});

Expand Down