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
16 changes: 15 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,18 @@ AUTH_PASSWORD=my_super_secret_password
SESSION_RESTORE_ENABLED=false

# Enable raw full URL feature (disabled by default).
RAW_FULL_URL=false
RAW_FULL_URL=false

# Base URL of the HTTP Inspector instance
# Default: http://localhost:3000
HTTP_INSPECTOR_URL=http://localhost:3000

# Default token ID to use for operations (optional)
# Can be either the full UUID or 8-character friendly ID
# Example: 550e8400-e29b-41d4-a716-446655440000 or abc12345
HTTP_INSPECTOR_TOKEN=

# Secret for accessing user tokens (optional)
# Required when accessing tokens created via the web UI
# Must be the session ID (UUID) that created the token
HTTP_INSPECTOR_SECRET=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ prisma/dev.*
nextjs-based/*
llm*.txt
nextjs-based/
__pycache__
147 changes: 147 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,35 @@ The UI is available at http://localhost:3001 and the container persists until yo
| **AUTH_PASSWORD** | No | **-** | Password required for login when authentication is enabled |
| **SESSION_RESTORE_ENABLED** | No | **true** | Enable restoring previous sessions by friendly ID |
| **RAW_FULL_URL** | No | **false** | Include full URL in raw request output |
| **ENABLE_LLM_ENDPOINT** | No | **false** | Enable LLM API endpoints for programmatic access |

## Python Client

A Python client library and CLI tool is available for programmatic access to the LLM API endpoints. The client provides:

- **Token Management**: Create, get, update, and delete webhook tokens
- **Request Inspection**: View captured requests and their details
- **Response Configuration**: Set custom responses for webhook endpoints
- **Environment Configuration**: Configure via environment variables

### Quick Start

```bash
# Get API information
./bin/http_inspector_client info

# Create a webhook token
./bin/http_inspector_client create

# View captured requests
./bin/http_inspector_client get <token-id>

# Get latest request
./bin/http_inspector_client latest <token-id>

# Configure custom response
./bin/http_inspector_client update <token-id> --status 201 --body "Created"
```

## API Reference

Expand Down Expand Up @@ -393,6 +422,124 @@ Report authentication status.
}
```

### LLM API

The LLM API provides programmatic access designed for automation tools and LLMs. **Must be enabled with `ENABLE_LLM_ENDPOINT=true`.**

**Key Features:**
- Tokens created via this API are associated with a static LLM session
- No authentication required for LLM-created tokens
- User tokens can be accessed read-only with `?secret=UUID` parameter
- Tokens identified by UUID or 8-character friendlyId

#### GET /api/llm
Get API information and documentation.

**Response:**
```json
{
"name": "HTTP Inspector LLM API",
"version": "1.0.0",
"description": "API endpoints designed for programmatic access",
"endpoints": [...],
"notes": [...]
}
```

#### POST /api/llm/token
Create a new payload token in the LLM session.

**Response:**
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"friendlyId": "abc12345",
"sessionId": "llm-session-static-id",
"createdAt": "2025-01-15T10:30:00.000Z",
"payloadUrl": "http://localhost:3000/api/payload/abc12345",
"responseEnabled": false,
"responseStatus": 200,
"responseHeaders": null,
"responseBody": null
}
```

#### GET /api/llm/token/:token
Get token details and all captured requests.

**Parameters:**
- `:token` - Token ID (UUID) or friendlyId (8-char)
- `?secret=UUID` - Required for user tokens (read-only access)

**Response:**
```json
{
"token": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"friendlyId": "abc12345",
"createdAt": "2025-01-15T10:30:00.000Z",
"payloadUrl": "http://localhost:3000/api/payload/abc12345"
},
"requests": [
{
"id": "650e8400-e29b-41d4-a716-446655440000",
"method": "POST",
"url": "/api/payload/abc12345?test=1",
"headers": {"content-type": "application/json"},
"contentType": "application/json",
"contentLength": 42,
"isBinary": false,
"body": "{\"test\":\"data\"}",
"clientIp": "192.168.1.100",
"remoteIp": "203.0.113.1",
"createdAt": "2025-01-15T10:30:00.000Z"
}
],
"total": 1
}
```

**Notes:** Binary data in request bodies is marked as `[Binary data not included]`.

#### GET /api/llm/token/:token/latest
Get the most recent request for a token.

**Parameters:**
- `:token` - Token ID (UUID) or friendlyId (8-char)
- `?secret=UUID` - Required for user tokens (read-only access)

**Response:** Single request object (same format as requests array above).

**Returns `404`** if no requests exist for the token.

#### PATCH /api/llm/token/:token
Update token response settings. **LLM tokens only** (user tokens not allowed).

**Parameters:**
- `:token` - Token ID (UUID) or friendlyId (8-char)

**Request Body:**
```json
{
"responseEnabled": true,
"responseStatus": 201,
"responseHeaders": "{\"Content-Type\":\"application/json\"}",
"responseBody": "{\"status\":\"created\"}"
}
```

All fields are optional. Configure what the webhook endpoint returns when it receives requests.

**Response:** `{ "ok": true }`

#### DELETE /api/llm/token/:token
Delete a token and all its associated requests. **LLM tokens only** (user tokens not allowed).

**Parameters:**
- `:token` - Token ID (UUID) or friendlyId (8-char)

**Response:** `{ "ok": true }`

### Standard Responses

Common status codes:
Expand Down
6 changes: 1 addition & 5 deletions app/components/RequestSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,6 @@ const handleCopyIp = async (request: RequestSummary) => {
return
}

if (false === (await copyText(clientIp))) {
return
}

notify({ title: 'Client IP copied', description: clientIp, color: 'success' })
await copyText(clientIp)
}
</script>
22 changes: 6 additions & 16 deletions app/components/token/ApiUrlsCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,35 @@
</template>

<div v-if="isOpen" class="flex flex-col gap-4 p-4 border-t border-gray-200 dark:border-gray-700">
<!-- Payload URL -->
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Webhook URL
</label>
<div class="flex gap-2">
<UInput :model-value="payloadUrl" readonly size="md" class="flex-1 font-mono text-xs" />
<UTooltip :text="copyPayloadState === 'copied' ? 'Copied!' : 'Copy URL'">
<UButton
:icon="copyPayloadState === 'copied' ? 'i-lucide-check' : 'i-lucide-copy'"
color="neutral"
variant="soft"
@click="handleCopyPayload" />
<UButton :icon="copyPayloadState === 'copied' ? 'i-lucide-check' : 'i-lucide-copy'"
color="neutral" variant="soft" @click="handleCopyPayload" />
</UTooltip>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
Use this URL to capture requests.
</p>
</div>

<!-- View API URL -->
<div class="space-y-2">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
Automation API URL
</label>
<div class="flex gap-2">
<UInput :model-value="viewUrl" readonly size="md" class="flex-1 font-mono text-xs" />
<UTooltip :text="copyViewState === 'copied' ? 'Copied!' : 'Copy URL'">
<UButton
:icon="copyViewState === 'copied' ? 'i-lucide-check' : 'i-lucide-copy'"
color="neutral"
variant="soft"
@click="handleCopyView" />
<UButton :icon="copyViewState === 'copied' ? 'i-lucide-check' : 'i-lucide-copy'" color="neutral"
variant="soft" @click="handleCopyView" />
</UTooltip>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
Read-only API for automation/LLMs. Returns all requests with bodies in JSON format.
Read-only API for automation/LLMs.
</p>
</div>
</div>
Expand Down Expand Up @@ -83,14 +75,13 @@ const payloadUrl = computed(() => {

const viewUrl = computed(() => {
const friendlyId = token.value?.friendlyId || ''
return `${origin.value}/api/view/${friendlyId}?secret=${props.tokenId}`
return `${origin.value}/api/llm/token/${friendlyId}?secret=${props.tokenId}`
})

const handleCopyPayload = async () => {
try {
await copyText(payloadUrl.value)
copyPayloadState.value = 'copied'
notify({ title: 'Webhook URL copied', description: payloadUrl.value, variant: 'success' })
setTimeout(() => copyPayloadState.value = 'idle', 1200)
} catch (error) {
console.error('Failed to copy URL:', error)
Expand All @@ -102,7 +93,6 @@ const handleCopyView = async () => {
try {
await copyText(viewUrl.value)
copyViewState.value = 'copied'
notify({ title: 'API URL copied', description: viewUrl.value, variant: 'success' })
setTimeout(() => copyViewState.value = 'idle', 1200)
} catch (error) {
console.error('Failed to copy URL:', error)
Expand Down
7 changes: 0 additions & 7 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,6 @@ const confirmDeleteToken = async () => {
tokenToDelete.value = null

await deleteTokenMutation(id)

notify({
title: 'Token deleted',
description: 'The token and its requests have been removed',
color: 'success',
})
}

const copyPayloadURL = async (id: string) => {
Expand All @@ -103,7 +97,6 @@ const copyPayloadURL = async (id: string) => {
const friendlyId = token?.friendlyId ?? shortSlug(id)
const url = `${origin}/api/payload/${friendlyId}`
await copyText(url)
notify({ title: 'URL copied', description: url, color: 'success', })
}

const deleteToken = (id: string) => {
Expand Down
7 changes: 3 additions & 4 deletions app/pages/token/[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</div>
</div>
<div class="grid gap-6 px-6 pb-6 lg:p-6">
<ApiUrlsCard :token-id="tokenId" />
<ApiUrlsCard v-if="llmEndpointEnabled" :token-id="tokenId" />
<ResponseSettingsCard :token-id="tokenId" />
<RawRequestCard :request="selectedRequest" :request-number="selectedRequestNumber" :token-id="tokenId" />
<RequestDetailsCard :request="selectedRequest" :request-number="selectedRequestNumber"
Expand Down Expand Up @@ -78,6 +78,8 @@ const { data: requests } = requestsStore.useRequestsList(tokenId)
const { mutateAsync: deleteRequestMutation } = requestsStore.useDeleteRequest()
const { mutateAsync: deleteAllRequestsMutation } = requestsStore.useDeleteAllRequests()

const llmEndpointEnabled = useRuntimeConfig().public?.llmEndpointEnabled === true

const selectedRequestId = ref<string | null>(null)
const incomingIds = ref<Set<string>>(new Set())
const copyState = ref<'idle' | 'copied'>('idle')
Expand Down Expand Up @@ -159,8 +161,6 @@ const handleDeleteRequest = async (id: string) => {
const firstRequest = requests.value && requests.value.length > 0 ? requests.value[0] : null
selectedRequestId.value = firstRequest ? firstRequest.id : null
}

notify({ title: 'Request deleted', variant: 'success' })
} catch (error) {
console.error('Failed to delete request:', error)
notify({ title: 'Failed to delete request', variant: 'error' })
Expand All @@ -175,7 +175,6 @@ const copyPayloadURL = async () => {
try {
await copyText(url)
copyState.value = 'copied'
notify({ title: 'Payload URL copied', description: url, variant: 'success' })
setTimeout(() => copyState.value = 'idle', 1200)
} catch (error) {
console.error('Failed to copy URL:', error)
Expand Down
Loading