# UtilsForAgents

> AI Agent Utility Hub — JSON Diff & Image Microservices at the Edge

## Base URL

https://utilsforagents.com

## Authentication

x402 payment protocol (HTTP 402 + USDC on Base). Agents pay $0.013 per call — no accounts or API keys needed.

## Endpoints

| Method | Path                        | Description                        |
|--------|-----------------------------|------------------------------------|
| POST   | /v1/diff                    | Compare two JSON objects           |
| POST   | /v1/image/exif-summary      | Extract EXIF metadata from JPEG    |
| POST   | /v1/image/scrub-metadata    | Strip metadata from JPEG/PNG       |
| POST   | /v1/html/to-markdown        | Convert HTML to clean Markdown     |
| POST   | /v1/html/fetch-markdown     | Fetch URL & convert to Markdown    |
| POST   | /v1/text/fetch-content      | Fetch .json/.md/.txt & extract text|
| POST   | /v1/url/metadata            | Extract page metadata (OG, etc.)   |
| GET    | /health                     | Service status                     |

## Upload Limits

Max upload size: 5 MB. Requests exceeding this return `413 Payload Too Large`.

---

## POST /v1/diff

Compare two JSON values and return a structured diff.

Content-Type: application/json

### Request

```json
{
  "left": {},
  "right": {},
  "options": {
    "includeUnchanged": false,
    "maxDepth": 64
  }
}
```

### Fields

- `left` (required): First JSON value to compare.
- `right` (required): Second JSON value to compare.
- `options.includeUnchanged` (optional, default: false): Count unchanged leaf nodes in stats.
- `options.maxDepth` (optional, default: 64, max: 256): Maximum recursion depth.

### Response

```json
{
  "ok": true,
  "stats": {
    "added": 0,
    "removed": 0,
    "changed": 0,
    "unchanged": 0,
    "processingTimeMs": 0.0
  },
  "diff": [
    {
      "op": "add | remove | replace",
      "path": "/json/pointer/path",
      "value": "for add/remove",
      "oldValue": "for replace",
      "newValue": "for replace"
    }
  ]
}
```

### Diff Operations

- `add`: Key exists in `right` but not in `left`. Includes `value`.
- `remove`: Key exists in `left` but not in `right`. Includes `value`.
- `replace`: Key exists in both but values differ. Includes `oldValue` and `newValue`.

### Paths

Paths use JSON Pointer (RFC 6901):
- `/` separates tokens
- `~0` encodes `~`
- `~1` encodes `/`

### Example

```bash
curl -X POST https://utilsforagents.com/v1/diff \
  -H "Content-Type: application/json" \
  -d '{"left":{"a":1,"b":2},"right":{"a":1,"b":3,"c":4}}'
```

```json
{
  "ok": true,
  "stats": {"added":1,"removed":0,"changed":1,"unchanged":0,"processingTimeMs":0.04},
  "diff": [
    {"op":"replace","path":"/b","oldValue":2,"newValue":3},
    {"op":"add","path":"/c","value":4}
  ]
}
```

---

## POST /v1/image/exif-summary

Extract essential EXIF metadata from a JPEG image.

Content-Type: image/jpeg (raw binary body)

### Request

Send the JPEG file as the raw request body.

```bash
curl -X POST https://utilsforagents.com/v1/image/exif-summary \
  --data-binary @photo.jpg -H "Content-Type: image/jpeg"
```

### Response

```json
{
  "ok": true,
  "processingTimeMs": 0.12,
  "exif": {
    "make": "Apple",
    "model": "iPhone 15 Pro",
    "software": "17.4",
    "dateTime": "2026:03:15 14:30:00",
    "dateTimeOriginal": "2026:03:15 14:30:00",
    "orientation": 1,
    "imageWidth": 4032,
    "imageHeight": 3024,
    "exposureTime": "1/120",
    "fNumber": 1.8,
    "iso": 100,
    "focalLength": 6.9,
    "gps": {
      "latitude": 40.446111,
      "longitude": -79.982222,
      "altitude": 350.5
    }
  }
}
```

### EXIF Fields (when present)

- `make`: Camera manufacturer.
- `model`: Camera model.
- `software`: Software version.
- `dateTime`: File modification date.
- `dateTimeOriginal`: Original capture date.
- `orientation`: EXIF orientation tag (1–8).
- `imageWidth`, `imageHeight`: Pixel dimensions.
- `exposureTime`: Shutter speed (e.g., "1/120").
- `fNumber`: Aperture (e.g., 1.8).
- `iso`: ISO sensitivity.
- `focalLength`: Focal length in mm.
- `gps.latitude`, `gps.longitude`: Decimal degrees.
- `gps.altitude`: Meters above sea level.

Fields are omitted from the response if not present in the image.

---

## POST /v1/image/scrub-metadata

Strip all metadata (EXIF, XMP, ICC, IPTC) from a JPEG or PNG image.

Content-Type: image/jpeg or image/png (raw binary body)

### Request

Send the image file as the raw request body.

```bash
curl -X POST https://utilsforagents.com/v1/image/scrub-metadata \
  --data-binary @photo.jpg -H "Content-Type: image/jpeg" -o clean.jpg
```

### Response

Returns the cleaned image as binary data.

- Content-Type: `image/jpeg` or `image/png` (matches input format).
- The image is pixel-identical — only metadata segments are removed.
- JPEG: Strips APP0–APP15 segments (JFIF, EXIF, XMP, ICC, IPTC).
- PNG: Strips ancillary chunks (tEXt, iTXt, zTXt, eXIf, iCCP, pHYs, tIME). Keeps rendering-critical chunks.

---

## POST /v1/html/to-markdown

Convert HTML to clean Markdown. Strips script, style, iframe, and other dangerous tags.

Content-Type: application/json OR text/html (raw body)

### Request (JSON)

```json
{
  "html": "<h1>Title</h1><p>Hello world</p>"
}
```

### Request (raw HTML)

Send HTML as the raw request body with any non-JSON content-type.

```bash
curl -X POST https://utilsforagents.com/v1/html/to-markdown \
  -H "Content-Type: text/html" \
  -d '<h1>Title</h1><p>Hello world</p>'
```

### Response

```json
{
  "ok": true,
  "processingTimeMs": 0.42,
  "charsBefore": 42,
  "charsAfter": 22,
  "markdown": "# Title\n\nHello world"
}
```

### Conversion Rules

- Strips: `<script>`, `<style>`, `<iframe>`, `<noscript>`, `<svg>`, `<canvas>`, `<template>`, `<object>`, `<embed>`, `<applet>`, `<form>` and their contents.
- Headings: `<h1>`–`<h6>` → `#`–`######`
- Inline: `<strong>`/`<b>` → `**`, `<em>`/`<i>` → `*`, `<del>`/`<s>` → `~~`, `<code>` → backtick
- Links: `<a href="url">text</a>` → `[text](url)`
- Images: `<img src="url" alt="text">` → `![text](url)`
- Code blocks: `<pre><code>` → fenced code blocks
- Lists: `<ol>`/`<ul>` → numbered/bulleted lists
- Tables: `<table>` → Markdown tables with alignment
- Blockquotes: `<blockquote>` → `>`
- Entities: Decodes named (`&amp;`, `&lt;`, etc.) and numeric (`&#123;`, `&#x7B;`) entities.

---

## POST /v1/html/fetch-markdown

Fetch a remote URL and convert the HTML response to Markdown.

Content-Type: application/json

### Request

```json
{
  "url": "https://example.com"
}
```

### Response

```json
{
  "ok": true,
  "processingTimeMs": 1.2,
  "sourceUrl": "https://example.com",
  "charsBefore": 1256,
  "charsAfter": 423,
  "markdown": "# Example Domain\n\nThis domain is for use in..."
}
```

### Limits

- Timeout: 5 seconds.
- Max response size: 2 MB.
- Only `http://` and `https://` protocols are allowed.
- URLs with embedded credentials are rejected.

### SSRF Protection

The following targets are blocked:
- Loopback: 127.0.0.0/8, ::1, localhost
- Private: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
- Link-local: 169.254.0.0/16 (includes cloud metadata 169.254.169.254)
- Internal DNS: *.internal, *.local, *.localhost

---

## POST /v1/text/fetch-content

Fetch a remote `.json`, `.md`, or `.txt` file and extract readable text as Markdown.

Content-Type: application/json

### Request

```json
{
  "url": "https://example.com/api/projects.json"
}
```

### Response

```json
{
  "ok": true,
  "processingTimeMs": 0.8,
  "sourceUrl": "https://example.com/api/projects.json",
  "detectedFormat": "json",
  "charsBefore": 2048,
  "charsAfter": 512,
  "markdown": "## Project One\n\nFirst project description...\n\n## Project Two\n\n..."
}
```

### Format Detection

- **json**: Content-Type `application/json` or `.json` URL extension. Recursively extracts text from title/body/content/description fields. Skips metadata keys (id, url, date, type, etc.).
- **html**: Content-Type `text/html` or `.html`/`.htm` extension. Full HTML→Markdown conversion.
- **markdown**: `.md` URL extension. Returned as-is (trimmed).
- **text**: Everything else. Returned as-is (trimmed).

### Limits

- Timeout: 5 seconds.
- Max response size: 2 MB.
- Same SSRF protection as `/v1/html/fetch-markdown`.

---

All errors follow RFC 9457 (Problem Details for HTTP APIs).

Content-Type: application/problem+json

| Status | Type Suffix            | Cause                            |
|--------|------------------------|----------------------------------|
| 400    | bad-request            | Body too small or malformed      |
| 400    | invalid-json           | Body is not valid JSON           |
| 404    | not-found              | Unknown route                    |
| 405    | method-not-allowed     | Wrong HTTP method                |
| 413    | payload-too-large      | Upload exceeds 5 MB              |
| 415    | unsupported-media-type | Not JPEG/PNG or no EXIF found    |
| 422    | invalid-schema         | Missing left or right fields     |

```json
{
  "type": "https://utilsforagents.com/errors/payload-too-large",
  "title": "Payload Too Large",
  "status": 413,
  "detail": "Upload exceeds the 5MB limit."
}
```

## Health Check

GET /health

```json
{
  "status": "ok",
  "service": "utilsforagents",
  "version": "1.5.0"
}
```
