This is the abridged developer documentation for EchoValue API # Overview > echoValue API - lightweight backend utilities for freelancers and small projects **echoValue** is a set of lightweight backend utilities designed for freelancers and small projects. No server setup, no infrastructure to manage — just a token and an HTTP call. It provides a small set of focused HTTP utilities for storage, webhooks, DNS inspection, URL metadata, caller-IP lookup, logs, and wallet management. * **Store small state** with the [Key-Value Store](/key-value/) * **Receive email or run cron-style callbacks** with [Webhooks](/webhook/) * **Inspect DNS, URLs, and caller IPs** with [Utilities](/api-cheat-sheet/) * **Track credits and calls** with [Token Management](/token/) and [Logs](/logs/) echoValue is API-first. If you prefer a browser interface for common operations, you can also use the optional dashboard at [dash.echovalue.dev](https://dash.echovalue.dev). Caution echoValue is not intended for storing sensitive data ## Quick Start [Section titled “Quick Start”](#quick-start) **1. Generate a token** (free, includes 100 credits): ```sh curl 'https://api.echovalue.dev/token' -d 'token=new' # → a1b2c3d4e5f6g7h8i9j0... ``` **2a. Store and retrieve a value:** ```sh curl 'https://api.echovalue.dev/kv/default/mykey' \ -H 'x-token: YOUR_TOKEN' \ -d 'hello world' # → OK curl 'https://api.echovalue.dev/kv/default/mykey' \ -H 'x-token: YOUR_TOKEN' # → hello world ``` **2b. Or configure an email-to-webhook bridge:** ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: YOUR_TOKEN' \ -H 'Content-Type: application/json' \ -d '{"webhookId":"default","url":"https://yourdomain.com/webhook"}' # → {"webhookId":"default","webhook":"https://yourdomain.com/webhook","email":"r_abcd1234@hook.echovalue.dev",...} # Emails sent to the returned mailbox address now POST to your URL ``` ## Services [Section titled “Services”](#services) | Service | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | **Key-Value Store** | Store, retrieve, and delete key-value pairs organized in buckets | | **Webhook** | Receive emails at an opaque mailbox address or run scheduled deliveries, while managing each integration through a public `webhookId` | | **DNS Lookup** | Run DNS record queries, propagation checks, reverse lookups, SSL inspection, and optional security enrichment | | **My IP** | Inspect the caller IP address together with geo metadata derived from the request | | **URL To Metadata** | Fetch metadata and optional summaries for a public URL | | **Logs** | Inspect your recent API call history | | **Token Management** | Check your credit balance and recharge | ## Reference [Section titled “Reference”](#reference) | Page | Use it for | | ---------------------------------------- | --------------------------------------------------------- | | [API Cheat Sheet](/api-cheat-sheet/) | Compare endpoints, auth, costs, and common use cases | | [Limits & Retention](/limits-retention/) | Check size, TTL, retention, and webhook scheduling limits | | [Errors](/errors/) | Understand status codes and common fixes | | [OpenAPI Specification](/openapi/) | Generate clients or import the API into tooling | ## AI Integration [Section titled “AI Integration”](#ai-integration) Machine-readable documentation is available at [`/llms.txt`](https://docs.echovalue.dev/llms.txt), [`/llms-small.txt`](https://docs.echovalue.dev/llms-small.txt), and [`/llms-full.txt`](https://docs.echovalue.dev/llms-full.txt). Agent setup files are documented in the [installation guide](https://github.com/njoylab/docs.echovalue.dev/blob/main/INSTALL.md). # API Cheat Sheet > Compare echoValue endpoints by method, auth, cost, and common use case Use this page when you know what you want to do and need the shortest path to the right endpoint. All paid endpoints use the `x-token` request header. New tokens include **100 free credits**. ## Endpoints [Section titled “Endpoints”](#endpoints) | Task | Method and path | Auth | Cost | Use case | | --------------------------- | -------------------------------- | ------------------ | ----------------- | ------------------------------------------------- | | Create token | `POST /token` | No | Free | Start using the API | | Get wallet info | `GET /token` | Yes | 1 credit | Check balance and wallet metadata | | Create recharge link | `GET /recharge?amount=` | Yes | Free | Add credits to a wallet | | Get logs | `GET /token/logs` | Yes | Free | Inspect recent API activity | | Get low balance settings | `GET /token/settings` | Yes | 1 credit | Read notification settings | | Update low balance settings | `PUT /token/settings` | Yes | 1 credit | Configure balance alerts | | Set key/value | `POST /kv//` | Yes | 1 credit | Store small string state | | Get key/value | `GET /kv//` | Yes | 1 credit | Retrieve stored state | | Delete key/value | `DELETE /kv//` | Yes | 1 credit | Remove stored state | | Create webhook | `POST /webhook` | Yes | 1 credit | Configure inbound email or scheduled delivery | | List webhooks | `GET /webhook` | Yes | 1 credit | See all webhook configs | | Get webhook | `GET /webhook/` | Yes | 1 credit | Inspect one webhook config | | Replace webhook | `PUT /webhook/` | Yes | 1 credit | Replace a full webhook config | | Patch webhook | `PATCH /webhook/` | Yes | 1 credit | Update selected webhook fields | | Delete webhook | `DELETE /webhook/` | Yes | 1 credit | Remove a webhook config | | Test webhook | `POST /webhook//test` | Yes | 1 credit | Send a test delivery | | Process inbound email | Mail to returned address | Configured webhook | 2-5 credits | Convert email into webhook delivery | | Scheduled webhook run | Configured schedule | Configured webhook | 2 credits | Run recurring HTTP callbacks | | DNS lookup | `POST /dns-lookup` | Yes | 5 credits | Inspect DNS records and enrichment | | Caller IP lookup | `GET /myip` | Yes | 1 credit | Read caller IP and geo metadata | | URL metadata | `POST /url-to-metadata` | Yes | 55 or 800 credits | Extract page metadata, optionally with AI summary | ## Pick The Right Tool [Section titled “Pick The Right Tool”](#pick-the-right-tool) | Need | Start with | | --------------------------------------------------------------- | --------------------------------------------------------- | | Shared state, counters, short notes, handoff between agents | [Key-Value Store](/key-value/) | | A generated email address that forwards messages to your system | [Webhook Inbound Email](/guides/webhook-inbound-email/) | | Cron-style callbacks without hosting a scheduler | [Webhook Scheduled Jobs](/guides/webhook-scheduled-jobs/) | | Slack, Discord, Teams, Telegram, or PagerDuty delivery | [Webhook Formats](/webhook/) | | Troubleshooting API calls and credit usage | [Logs](/logs/) | | Client generation or API import | [OpenAPI Specification](/openapi/) | ## Base URL [Section titled “Base URL”](#base-url) ```txt https://api.echovalue.dev ``` See [Authentication](/authentication/) for the `x-token` header and [Response Headers](/response-headers/) for `x-cost` and `x-balance`. # Authentication > How to authenticate requests to the echoValue API using x-token header All API requests must include an `x-token` header with your API token (minimum 10 characters). ```plaintext x-token: YOUR_TOKEN ``` ## Example [Section titled “Example”](#example) * cURL ```sh curl 'https://api.echovalue.dev/kv/default/mykey' \ -H 'x-token: mytoken' ``` * JavaScript ```js fetch('https://api.echovalue.dev/kv/default/mykey', { headers: { 'x-token': 'mytoken' } }); ``` * Python ```python import requests requests.get('https://api.echovalue.dev/kv/default/mykey', headers={'x-token': 'mytoken'} ) ``` * PHP ```php ``` * Go ```go package main import "net/http" func main() { req, _ := http.NewRequest("GET", "https://api.echovalue.dev/kv/default/mykey", nil) req.Header.Set("x-token", "mytoken") client := &http.Client{} client.Do(req) } ``` ## Response Headers [Section titled “Response Headers”](#response-headers) Paid endpoints return `x-cost` and `x-balance` headers. See [Response Headers](/response-headers/) for details and language examples. ## Error Responses [Section titled “Error Responses”](#error-responses) | Status | Meaning | | ---------------------- | ------------------------------------------------------- | | `401 Unauthorized` | The `x-token` header is missing or the token is invalid | | `402 Payment Required` | Your wallet has no credits remaining | See the [Errors](/errors/) page for the full list of error codes. # DNS Lookup > Run DNS lookup, propagation checks, SSL inspection, and enrichment through the echoValue DNS Lookup API `POST https://api.echovalue.dev/dns-lookup` Runs DNS lookup and enrichment through echoValue. The request is authenticated with `x-token`, charged by echoValue, and logged. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | -------------- | -------------------------- | | `x-token` | Your API token | | `content-type` | Must be `application/json` | **Body:** | Field | Type | Required | Description | | ---------------------- | --------- | -------- | ---------------------------------------------------------------------------------------------- | | `domains` | string\[] | Yes | Domain names or IP addresses to inspect | | `recordTypes` | string\[] | No | DNS record types to query: `A`, `AAAA`, `MX`, `CNAME`, `TXT`, `NS`, `PTR`, `SOA`, `CAA`, `SRV` | | `checkPropagation` | boolean | No | Query multiple public resolvers to compare propagation | | `propagationServers` | string\[] | No | Override the resolver list used for propagation checks | | `performReverseLookup` | boolean | No | Run PTR lookups for returned IPs or direct IP input | | `enableEnrichment` | boolean | No | Enable SPF, DMARC, DKIM, provider, and security analysis | | `enableSslInspection` | boolean | No | Inspect live TLS certificate metadata for HTTPS targets | | `timeout` | integer | No | Per-query timeout in milliseconds. Min: `1000`. Max: `60000`. Default: `5000` | | `maxRetries` | integer | No | Retry attempts for failed lookups. Min: `0`. Max: `10`. Default: `3` | | `retryDelay` | integer | No | Delay between retries in milliseconds. Min: `0`. Max: `100000`. Default: `1000` | | `includeMetadata` | boolean | No | Include additional lookup metadata | The service supports record types `A`, `AAAA`, `MX`, `CNAME`, `TXT`, `NS`, `PTR`, `SOA`, `CAA`, and `SRV`. **Examples:** ```sh curl 'https://api.echovalue.dev/dns-lookup' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "domains": ["example.com"], "recordTypes": ["A", "MX", "TXT"], "checkPropagation": true, "performReverseLookup": true, "enableEnrichment": true, "enableSslInspection": true }' ``` ## Response [Section titled “Response”](#response) `200 OK` returns the DNS lookup result as JSON. ```json { "summary": { "totalDomains": 1, "successfulLookups": 1, "failedLookups": 0, "recordTypes": ["A", "MX", "TXT"], "propagationCheckEnabled": false }, "results": [ { "domain": "example.com", "lookupResult": { "domain": "example.com", "timestamp": "2026-04-22T09:15:00.000Z", "records": { "A": [ { "type": "A", "address": "93.184.216.34", "ttl": 3600 } ], "MX": [ { "type": "MX", "exchange": ".", "priority": 0 } ], "TXT": [ { "type": "TXT", "entries": ["v=spf1 -all"], "ttl": 1800 } ] }, "spf_valid": true, "spf_strict": true, "dmarc_policy": "reject", "has_dkim": false, "email_security_score": 85, "ssl_certificate_expires_at": "2026-07-20T12:00:00.000Z", "ssl_days_until_expiry": 89, "warnings": ["No DKIM records detected"], "recommendations": ["Configure DKIM signing for email authentication"], "metadata": { "queryTime": 124, "totalRecords": 3 } } } ] } ``` **Top-level fields:** | Field | Type | Description | | --------- | ------ | --------------------------------------------- | | `summary` | object | Aggregate information about the whole request | | `results` | array | One item per requested domain or IP address | **`summary` fields:** | Field | Type | Description | | ------------------------- | --------- | --------------------------------------- | | `totalDomains` | integer | Number of requested domains or IPs | | `successfulLookups` | integer | Number of successful lookups | | `failedLookups` | integer | Number of failed lookups | | `recordTypes` | string\[] | Record types requested for this run | | `propagationCheckEnabled` | boolean | Whether propagation checks were enabled | **`results[]` fields:** | Field | Type | Description | | -------------------- | ------ | ------------------------------------------ | | `domain` | string | Requested domain or IP address | | `lookupResult` | object | Main DNS lookup result for that target | | `propagationResults` | array | Present only when `checkPropagation: true` | **Common `lookupResult` fields:** | Field | Type | Description | | ---------------------- | ----------------- | ------------------------------------------------------------- | | `domain` | string | Resolved target | | `timestamp` | string (ISO 8601) | When the lookup completed | | `records` | object | DNS records grouped by type | | `reverseLookup` | object | PTR lookup result when reverse lookup is enabled and relevant | | `mx_provider` | string or null | Best-effort provider inferred from MX records | | `spf_valid` | boolean | Whether SPF parsing found a valid record | | `spf_strict` | boolean | Whether the SPF policy is strict | | `dmarc_policy` | string | DMARC policy when detected | | `has_dkim` | boolean | Whether DKIM records were detected | | `email_security_score` | number | Best-effort enrichment score from 0 to 100 | | `warnings` | string\[] | Warnings from SSL or email-security analysis | | `recommendations` | string\[] | Suggested follow-up actions | | `metadata` | object | Request metadata such as query time and record count | **Typed DNS records:** * `A` and `AAAA` records include `address` and optional `ttl` * `MX` records include `exchange`, `priority`, and optional `ttl` * `TXT` records include `entries[]` and optional `ttl` * `CNAME`, `NS`, and `PTR` records include `value` and optional `ttl` * `SOA`, `CAA`, and `SRV` records expose their standard protocol-specific fields The response is partially typed in the OpenAPI spec, but some nested enrichment data can vary by target and enabled options. **Response headers:** | Header | Description | | ----------- | --------------------------------- | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed for this request | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ---------------------------------------- | | `200` | Lookup completed successfully | | `400` | Invalid request body or validation error | | `401` | Invalid token | | `402` | Wallet has insufficient credits | | `405` | Method not allowed | | `500` | Internal service error | | `502` | DNS lookup service unavailable | | `503` | DNS lookup service unavailable | ## Notes [Section titled “Notes”](#notes) * `400` responses can still return a structured JSON body. * `5xx` service responses consume `0` credits. * The endpoint accepts both domain names and IP addresses. * Propagation checks and enrichment can take noticeably longer than a basic DNS query. * The interactive service at [lookup.echovalue.dev](https://lookup.echovalue.dev) exposes the same analysis features in a browser UI. # Errors > HTTP error codes returned by the echoValue API The API uses standard HTTP status codes. Error responses are returned as plain text with a short description. **Example error response:** ```plaintext Key Not Found ``` ## Response Format [Section titled “Response Format”](#response-format) Error bodies are plain text, not JSON. Handle them as text unless an endpoint page documents a different response body. ```sh curl -i 'https://api.echovalue.dev/kv/default/missing' \ -H 'x-token: mytoken' ``` ```http HTTP/1.1 404 Not Found x-cost: 1 x-balance: 97 Key Not Found ``` ## 4xx Client Errors [Section titled “4xx Client Errors”](#4xx-client-errors) | Code | Name | When it occurs | What to check | | ----- | ------------------ | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | `400` | Bad Request | The request is malformed, missing required parameters, or uses an invalid field combination | Body shape, content type, required fields, URL/query parameters | | `401` | Unauthorized | The `x-token` header is missing, too short, or invalid | Send the exact token in `x-token`; create a new token if needed | | `402` | Payment Required | The wallet has insufficient credits | Check balance with `GET /token` or create a recharge link | | `404` | Not Found | The key, webhook, or resource does not exist | Confirm the bucket/key, `webhookId`, and path | | `405` | Method Not Allowed | The HTTP method is not supported for this endpoint | Compare the method with the endpoint reference | | `409` | Conflict | A resource already exists | Use a different `webhookId` or update the existing webhook | ## 5xx Server Errors Documented In OpenAPI [Section titled “5xx Server Errors Documented In OpenAPI”](#5xx-server-errors-documented-in-openapi) | Code | Name | When it occurs | What to do | | ----- | --------------------- | ------------------------------------------------- | ---------------------------------------- | | `500` | Internal Server Error | Unexpected server-side failure | Retry later and check [Logs](/logs/) | | `502` | Bad Gateway | A utility request could not complete successfully | Retry later or reduce request complexity | | `503` | Service Unavailable | The requested service is temporarily unavailable | Retry later | ## Common Scenarios [Section titled “Common Scenarios”](#common-scenarios) | Situation | Status code | Typical body | | -------------------------------------------------- | -------------- | ------------------------------------- | | Missing `x-token` header | `401` | `Unauthorized` | | Token doesn’t exist | `401` | `Unauthorized` | | No credits left | `402` | `Insufficient credits` | | GET on a key that was never set | `404` | `Key Not Found` | | Create a webhook with an existing `webhookId` | `409` | Short conflict message | | GET webhook list when none is configured | `200` | JSON with an empty `webhooks` array | | DELETE webhook with missing or unknown `webhookId` | `400` or `404` | Short validation or not-found message | | Calling an endpoint with the wrong HTTP method | `405` | Short method error | ## Notes [Section titled “Notes”](#notes) * Use [Response Headers](/response-headers/) to read `x-cost` and `x-balance` when they are present. * Use [Logs](/logs/) to inspect recent failed calls for the same token. * `429 Too Many Requests` can occur when a caller exceeds protective rate limits. # Agent Shared State > Give your AI agents shared state using the echoValue key-value API. Copy-paste examples for Python and TypeScript. Give your AI agents shared state. Copy, paste, run. If you want the installation guide for Claude, Codex, ChatGPT, Cursor, and Continue, see [INSTALL.md](https://github.com/njoylab/docs.echovalue.dev/blob/main/INSTALL.md). ## How it works [Section titled “How it works”](#how-it-works) 1. **Define** — Register `save_state` and `read_state` as tools your LLM can call. 2. **Handle** — When the LLM calls a tool, your handler sends the request to the echoValue API. 3. **Share** — Any agent with the same token can read and write. Shared state, zero config. ## Full example [Section titled “Full example”](#full-example) * Python ```python # pip install anthropic requests import requests from anthropic import Anthropic # Get a free token: curl https://api.echovalue.dev/token -d 'token=new' TOKEN = "your-echovalue-token" BASE = "https://api.echovalue.dev/kv" # 1. Define the tools tools = [ { "name": "save_state", "description": "Save a key-value pair to shared agent state", "input_schema": { "type": "object", "properties": { "group": {"type": "string", "description": "Namespace / bucket"}, "key": {"type": "string", "description": "Identifier"}, "value": {"type": "string", "description": "Data to store"}, "ttl": {"type": "integer", "description": "Expiry in seconds"} }, "required": ["group", "key", "value"] } }, { "name": "read_state", "description": "Read a value from shared agent state", "input_schema": { "type": "object", "properties": { "group": {"type": "string", "description": "Namespace / bucket"}, "key": {"type": "string", "description": "Identifier"} }, "required": ["group", "key"] } } ] # 2. Handle tool calls → echoValue API def run_tool(name, args): headers = {"x-token": TOKEN} if name == "save_state": ttl = args.get("ttl", 86400) r = requests.post( f"{BASE}/{args['group']}/{args['key']}?ttl={ttl}", data=args["value"], headers=headers) return r.text if name == "read_state": r = requests.get( f"{BASE}/{args['group']}/{args['key']}", headers=headers) return r.text # 3. Run the agent client = Anthropic() response = client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, tools=tools, messages=[{"role": "user", "content": "Save status=done for the import pipeline"}] ) # 4. Process tool calls from the response for block in response.content: if block.type == "tool_use": result = run_tool(block.name, block.input) print(f"{block.name} → {result}") # save_state → OK ``` * TypeScript ```typescript // npm install @anthropic-ai/sdk import Anthropic from "@anthropic-ai/sdk"; // Get a free token: curl https://api.echovalue.dev/token -d 'token=new' const TOKEN = "your-echovalue-token"; const BASE = "https://api.echovalue.dev/kv"; // 1. Define the tools const tools: Anthropic.Tool[] = [ { name: "save_state", description: "Save a key-value pair to shared agent state", input_schema: { type: "object" as const, properties: { group: { type: "string", description: "Namespace / bucket" }, key: { type: "string", description: "Identifier" }, value: { type: "string", description: "Data to store" }, ttl: { type: "integer", description: "Expiry in seconds" }, }, required: ["group", "key", "value"], }, }, { name: "read_state", description: "Read a value from shared agent state", input_schema: { type: "object" as const, properties: { group: { type: "string", description: "Namespace / bucket" }, key: { type: "string", description: "Identifier" }, }, required: ["group", "key"], }, }, ]; // 2. Handle tool calls → echoValue API async function runTool(name: string, args: Record) { const headers = { "x-token": TOKEN }; if (name === "save_state") { const ttl = args.ttl ?? 86400; const res = await fetch( `${BASE}/${args.group}/${args.key}?ttl=${ttl}`, { method: "POST", body: args.value, headers } ); return res.text(); } if (name === "read_state") { const res = await fetch( `${BASE}/${args.group}/${args.key}`, { headers } ); return res.text(); } } // 3. Run the agent const client = new Anthropic(); const response = await client.messages.create({ model: "claude-sonnet-4-5", max_tokens: 1024, tools, messages: [{ role: "user", content: "Save status=done for the import pipeline" }], }); // 4. Process tool calls from the response for (const block of response.content) { if (block.type === "tool_use") { const result = await runTool(block.name, block.input as Record); console.log(`${block.name} → ${result}`); // save_state → OK } } ``` # Webhook Inbound Email > Receive email at an opaque address and forward it to Slack, Discord, PagerDuty, or your own endpoint. Use an inbound email webhook when you want echoValue to give you a mailbox address and forward each received email to your webhook URL. Use [webhook.echovalue.dev](https://webhook.echovalue.dev) when you need a temporary HTTPS webhook URL for testing. ## What you get [Section titled “What you get”](#what-you-get) * An opaque `email` address such as `r_xxxxx@hook.echovalue.dev` * Optional email attachment forwarding via `includeAttachments` * Optional format adapters such as `slack`, `discord`, `teams`, `telegram`, `pagerduty`, or `custom` * The inbound email payload documented in [Webhook Payloads](/webhook/payload/) ## Minimal setup [Section titled “Minimal setup”](#minimal-setup) * cURL ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "webhookId": "support-alerts", "url": "https://hooks.slack.com/services/XXX/YYY/ZZZ", "format": "slack", "includeAttachments": false }' ``` * JavaScript ```js fetch('https://api.echovalue.dev/webhook', { method: 'POST', headers: { 'x-token': 'mytoken', 'Content-Type': 'application/json' }, body: JSON.stringify({ webhookId: 'support-alerts', url: 'https://hooks.slack.com/services/XXX/YYY/ZZZ', format: 'slack', includeAttachments: false }) }); ``` ## Example response [Section titled “Example response”](#example-response) ```json { "webhookId": "support-alerts", "webhook": "https://hooks.slack.com/services/XXX/YYY/ZZZ", "headers": {}, "format": "slack", "options": {}, "includeAttachments": false, "email": "r_abcd1234@hook.echovalue.dev", "hash": "a1b2c3d4e5f6..." } ``` ## Field rules [Section titled “Field rules”](#field-rules) * Use `format`, `template`, `options`, and `includeAttachments` only in inbound email mode. * Do not send `schedule` in this mode. * If `format` is omitted, the raw inbound email payload is forwarded as JSON. ## Delivery Limits [Section titled “Delivery Limits”](#delivery-limits) * Inbound email payloads are limited to 1 MiB after parsing. * echoValue outbound webhook delivery has a 10s timeout, follows at most 10 redirects, and has no application-level retry. * Delivery succeeds only when your webhook returns a 2xx response. 4xx/5xx responses are recorded as failed delivery. * Inbound email delivery is best effort. Design your webhook to accept duplicate messages and recover from missed deliveries. * echoValue does not retain routed email content after webhook delivery. ## Good fits [Section titled “Good fits”](#good-fits) * Turn support mailbox traffic into Slack or Discord messages * Trigger PagerDuty incidents from inbound alert emails * Forward normalized email data into your own application See also: * [Create Webhook](/webhook/create/) * [Webhook Format: Slack](/webhook/formats/slack/) * [Webhook Payloads](/webhook/payload/) # Webhook Scheduled Jobs > Trigger your webhook URL on a recurring schedule. Use a scheduled webhook when you want echoValue to call your webhook URL on a cron schedule. Use [crontab.echovalue.dev](https://crontab.echovalue.dev) to generate the `schedule.cron` value. ## What you get [Section titled “What you get”](#what-you-get) * A recurring webhook trigger based on a 5-field cron expression * An IANA timezone such as `Europe/Rome` * The scheduled-run payload documented in [Webhook Payloads](/webhook/payload/) * 2 credits charged for each scheduled execution ## Minimal setup [Section titled “Minimal setup”](#minimal-setup) * cURL ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "webhookId": "daily-sync", "url": "https://example.com/hooks/daily-sync", "schedule": { "cron": "0 9 * * *", "timezone": "Europe/Rome" } }' ``` * JavaScript ```js fetch('https://api.echovalue.dev/webhook', { method: 'POST', headers: { 'x-token': 'mytoken', 'Content-Type': 'application/json' }, body: JSON.stringify({ webhookId: 'daily-sync', url: 'https://example.com/hooks/daily-sync', schedule: { cron: '0 9 * * *', timezone: 'Europe/Rome' } }) }); ``` ## Example response [Section titled “Example response”](#example-response) ```json { "webhookId": "daily-sync", "webhook": "https://example.com/hooks/daily-sync", "headers": {}, "schedule": { "cron": "0 9 * * *", "timezone": "Europe/Rome", "active": true, "nextRunAt": "2026-04-24T07:00:00.000Z" }, "hash": "a1b2c3d4e5f6..." } ``` ## Field rules [Section titled “Field rules”](#field-rules) * `schedule` is required in this mode. * Do not send `format`, `template`, `options`, or `includeAttachments` together with `schedule`. * Scheduled webhooks do not return an `email` field. Caution The first scheduled run must be within 30 days from creation, and the interval between runs must never exceed 30 days. ## Good fits [Section titled “Good fits”](#good-fits) * Daily sync jobs * Scheduled digests or reminders * Lightweight cron-style automation against your own endpoint See also: * [Create Webhook](/webhook/create/) * [Update Webhook](/webhook/update/) * [Webhook Payloads](/webhook/payload/) # Key-Value Store > Store, retrieve, and delete key-value pairs with the echoValue API The key-value store lets you persist string data organized in named buckets. All operations require authentication via the `x-token` header. **Base URL:** `https://api.echovalue.dev/kv//` | URL Parameter | Description | Max length | | ------------- | ------------------------------------------- | ------------- | | `bucket` | Logical namespace for keys (e.g. `default`) | 30 characters | | `key` | Key identifier | 30 characters | ## Pricing [Section titled “Pricing”](#pricing) | Operation | Cost | | ---------------- | -------- | | Set key/value | 1 credit | | Get key/value | 1 credit | | Delete key/value | 1 credit | ## Limits [Section titled “Limits”](#limits) | Constraint | Value | | -------------------------- | ----------------------------------------------- | | Max key/bucket name length | 30 characters | | Max value length | 1000 characters (\~1 KB) | | Max TTL | 2,592,000 seconds (30 days) | | Default TTL (if omitted) | 30 days | | Bucket naming | Cannot start with `reserved-` or end with `---` | # Delete Key/Value > Delete a key and its value from the echoValue key-value store `DELETE https://api.echovalue.dev/kv//` Deletes a key and its associated value. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | ```sh curl 'https://api.echovalue.dev/kv/default/mykey' \ -H 'x-token: mytoken' \ -X DELETE ``` ## Response [Section titled “Response”](#response) `200 OK` ```plaintext OK ``` **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ------------------------ | | `200` | Key deleted successfully | | `400` | Invalid parameters | | `401` | Invalid token | | `402` | Insufficient credits | | `404` | Key not found | # Get Key/Value > Retrieve a stored value by key from the echoValue key-value store `GET https://api.echovalue.dev/kv//` Retrieves the stored value for a key. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | **Path parameters:** | Parameter | Type | Description | | --------- | ------ | ------------------------------------- | | `bucket` | string | Logical namespace. Max 30 characters. | | `key` | string | Key identifier. Max 30 characters. | ```sh curl 'https://api.echovalue.dev/kv/default/mykey' \ -H 'x-token: mytoken' ``` ## Response [Section titled “Response”](#response) `200 OK` — The stored value as plain text. ```plaintext hello world ``` **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | --------------------------------- | | `200` | Value returned | | `400` | Invalid parameters | | `401` | Invalid token | | `402` | Insufficient credits | | `404` | Key does not exist or has expired | ## Notes [Section titled “Notes”](#notes) * The response body is plain text, not JSON. # Set Key/Value > Store a value for a key in the echoValue key-value store `POST https://api.echovalue.dev/kv//` Stores a value for the given key. If the key already exists, its value is overwritten. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | **Path parameters:** | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------------------------------------------- | | `bucket` | string | Logical namespace. Max 30 characters. Cannot start with `reserved-` or end with `---`. | | `key` | string | Key identifier. Max 30 characters. | **Query parameters:** | Parameter | Type | Description | Default | | --------- | ------- | ---------------------------------------------------------------------------- | ------- | | `ttl` | integer | Time-to-live in seconds. Min: `0` (= max 30 days). Max: `2592000` (30 days). | 30 days | **Body** (`text/plain`): The value to store. Max **1000 characters**. ```sh curl 'https://api.echovalue.dev/kv/default/mykey' \ -H 'x-token: mytoken' \ -d 'hello world' # With 30-second TTL curl 'https://api.echovalue.dev/kv/default/mykey?ttl=30' \ -H 'x-token: mytoken' \ -d 'hello world' ``` ## Response [Section titled “Response”](#response) `200 OK` ```plaintext OK ``` **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ------------------------- | | `200` | Value stored successfully | | `400` | Malformed request | | `401` | Invalid token | | `402` | Insufficient credits | ## Notes [Section titled “Notes”](#notes) * `ttl=0` means the maximum TTL, which is 30 days. * Values are stored as plain text and overwrite any existing value for the same bucket/key. # Limits & Retention > Size, TTL, retention, and scheduling limits for echoValue APIs This page collects cross-cutting limits. Endpoint pages remain the source of truth for request and response fields. ## Storage [Section titled “Storage”](#storage) | Constraint | Value | | ------------------ | ----------------------------------------------- | | Key name length | 30 characters | | Bucket name length | 30 characters | | Value length | 1000 characters, about 1 KB | | Default key TTL | 30 days | | Maximum key TTL | 2,592,000 seconds, 30 days | | Bucket naming | Cannot start with `reserved-` or end with `---` | ## Tokens And Logs [Section titled “Tokens And Logs”](#tokens-and-logs) | Constraint | Value | | -------------------------------- | --------------- | | New token credits | 100 credits | | Token inactivity before deletion | 2 years | | Log retention | 7 days | | Log deletion after expiration | Within 24 hours | | Default log count | 5 | | Maximum log count | 100 | Caution When an unused token expires, associated data is deleted. ## Webhooks [Section titled “Webhooks”](#webhooks) | Constraint | Value | | ----------------------------- | --------------------------------------------- | | Webhook URL | Must be HTTPS and publicly reachable | | Localhost/private addresses | Not accepted | | Delivery timeout | 10 seconds | | Inbound email payload | 1 MiB after parsing | | Custom template size | 2048 bytes | | Custom template depth | 5 levels | | Format options | Up to 10 keys, values up to 500 characters | | Schedule expression | 5-field cron: `minute hour day month weekday` | | Schedule timezone | IANA timezone, for example `Europe/Rome` | | First scheduled run | Must occur within 30 days | | Maximum interval between runs | 30 days | ## Utilities [Section titled “Utilities”](#utilities) | API | Limits | | --------------- | --------------------------------------------------------------------------------------------------------- | | DNS Lookup | `timeout` from 1000 to 60000 ms, `maxRetries` from 0 to 10, `retryDelay` from 0 to 100000 ms | | URL To Metadata | Target URL must be publicly reachable; nested `data` fields are optional and should be parsed defensively | | My IP | Returns metadata derived from the request; unavailable fields may be empty or omitted | ## Billing Notes [Section titled “Billing Notes”](#billing-notes) Most authenticated API calls consume credits even when the request returns a client error. `GET /token/logs` is free. Some utility endpoints do not charge for `5xx` service responses; see the endpoint page for the exact cost rule. # Logs > Retrieve API call history for your echoValue token `GET https://api.echovalue.dev/token/logs` Returns a list of recent API calls made with your token. Logs have a TTL of 7 days and are deleted within 24 hours after expiration. ## Request [Section titled “Request”](#request) **Request headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | **Query parameters:** | Parameter | Type | Description | Default | | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `n` | integer | Number of log entries to retrieve. Min: `1`. Default: `5`. | `5` | | `type` | string | Optional endpoint filter. Common values include `kv`, `token`, `settings`, `webhook`, `webhook-delivery`, `url-to-metadata`, `myip`, `service`, and `manage`. | — | Supported `type` filters are implementation-driven. Common values are: | Type | Description | | ------------------ | -------------------------------------------------------------- | | `kv` | Key-value operations | | `token` | Wallet info requests | | `settings` | Low-balance settings reads and updates | | `webhook` | Webhook management audit logs | | `webhook-delivery` | Webhook delivery and test logs, including scheduled deliveries | | `url-to-metadata` | URL metadata and scraping requests | | `myip` | Caller IP lookup requests | | `service` | Recharge operations | | `manage` | Management operations | For `webhook-delivery` logs, `path` is the `webhookId`. ## Response [Section titled “Response”](#response) `200 OK` — JSON object. ```json { "logs": [ { "id": "jd9kImh8U4In3odfNeKf", "method": "GET", "path": "/kv/default/mykey", "endpoint": "kv", "status_code": 200, "response_time_ms": 513, "error": "Key Not Found", "timestamp": "2023-12-07T12:14:16.124481Z", "expiration": "2023-12-14T12:14:16.12448Z", "cost": 1, "balance": 97 } ], "n": 1 } ``` | Field | Type | Description | | ------ | ------- | ----------------------------------- | | `n` | integer | Number of entries actually returned | | `logs` | array | Array of log entries | **Log entry fields:** | Field | Type | Description | | ------------------ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Unique log entry ID | | `method` | string | HTTP method used (`GET`, `POST`, `DELETE`) | | `path` | string | Full request path including query parameters. For `webhook-delivery` logs, this is the `webhookId`. | | `endpoint` | string | API category such as `kv`, `token`, `settings`, `webhook`, `webhook-delivery`, `url-to-metadata`, `myip`, `service`, or `manage` | | `status_code` | integer | HTTP status code returned by the request | | `response_time_ms` | integer | Response time in milliseconds for the request | | `error` | string | Error message, if any (omitted on success) | | `timestamp` | string (ISO 8601) | When the request was made | | `expiration` | string (ISO 8601) | When this log entry expires (7 days after creation) | | `cost` | integer | Credits deducted for that request | | `balance` | integer | Wallet balance after that request | ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ------------- | | `200` | Logs returned | | `401` | Invalid token | **Response headers:** | Header | Description | | -------- | --------------------------------------------------------------------------------------------- | | `x-cost` | Declared in the current OpenAPI file. For this free endpoint, treat it as informational only. | ## Examples [Section titled “Examples”](#examples) * cURL ```sh # Get all logs (default 5) curl 'https://api.echovalue.dev/token/logs?n=5' \ -H 'x-token: mytoken' # Get only webhook deliveries and tests curl 'https://api.echovalue.dev/token/logs?type=webhook-delivery' \ -H 'x-token: mytoken' ``` * JavaScript ```js // Get all logs fetch('https://api.echovalue.dev/token/logs?n=5', { headers: { 'x-token': 'mytoken' } }) .then(response => response.json()) .then(data => { console.log(`Returned ${data.n} logs`); (data.logs || []).forEach(l => { console.log(`${l.timestamp} ${l.method} ${l.endpoint || l.path} status=${l.status_code} time=${l.response_time_ms}ms cost=${l.cost} balance=${l.balance} id=${l.id}`); }); }); // Get only webhook delivery logs fetch('https://api.echovalue.dev/token/logs?type=webhook-delivery', { headers: { 'x-token': 'mytoken' } }) .then(response => response.json()) .then(data => { console.log(`Returned ${data.n} webhook delivery logs`); }); ``` * Python ```python import requests # Get all logs response = requests.get('https://api.echovalue.dev/token/logs', headers={'x-token': 'mytoken'}, params={'n': 5} ) data = response.json() print(f"Returned {data.get('n')} logs") for l in data.get('logs', []): print(f"{l.get('timestamp')} {l.get('method')} {l.get('endpoint') or l.get('path')} status={l.get('status_code')} time={l.get('response_time_ms')}ms cost={l.get('cost')} balance={l.get('balance')} id={l.get('id')}") # Get only webhook delivery logs response = requests.get('https://api.echovalue.dev/token/logs', headers={'x-token': 'mytoken'}, params={'type': 'webhook-delivery'} ) ``` * PHP ```php ``` * Go ```go package main import ( "encoding/json" "fmt" "io" "net/http" ) func main() { // Get all logs req, _ := http.NewRequest("GET", "https://api.echovalue.dev/token/logs?n=5", nil) req.Header.Set("x-token", "mytoken") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var result map[string]interface{} json.Unmarshal(body, &result) if logs, ok := result["logs"].([]interface{}); ok { fmt.Printf("Returned %v logs\n", result["n"]) for _, li := range logs { l := li.(map[string]interface{}) fmt.Printf("%v %v %v status=%v time=%vms cost=%v balance=%v id=%v\n", l["timestamp"], l["method"], l["endpoint"], l["status_code"], l["response_time_ms"], l["cost"], l["balance"], l["id"]) } } // Get only webhook delivery logs req2, _ := http.NewRequest("GET", "https://api.echovalue.dev/token/logs?type=webhook-delivery", nil) req2.Header.Set("x-token", "mytoken") resp2, _ := client.Do(req2) defer resp2.Body.Close() } ``` ## Notes [Section titled “Notes”](#notes) * Logs can appear with a delay of a few seconds after the original request. * Entries expire after 7 days and are deleted within 24 hours after expiration. # My IP > Retrieve the caller IP address and geo metadata for your current request `GET https://api.echovalue.dev/myip` Returns the IP address seen by echoValue for the current request, plus geo metadata derived from the request headers. This endpoint is useful when you need to: * verify the public IP used by your server or client * detect whether the current request arrived over IPv4 or IPv6 * inspect geo metadata such as city, country, timezone, and region ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | **Examples:** ```sh curl 'https://api.echovalue.dev/myip' \ -H 'x-token: mytoken' ``` ## Response [Section titled “Response”](#response) **Response:** `200` — JSON object. ```json { "ip": "203.0.113.42", "ipv6": "", "isCurrentIpv6": false, "userAgent": "curl/8.7.1", "acceptLanguage": "en-US,en;q=0.9", "city": "Rome", "country": "IT", "continent": "EU", "latitude": "41.9028", "longitude": "12.4964", "region": "Lazio", "regionCode": "62", "timezone": "Europe/Rome", "postalCode": "00100" } ``` | Field | Type | Description | | ---------------- | ------- | ----------------------------------------------------------- | | `ip` | string | IPv4 address when available. Empty for native IPv6 callers. | | `ipv6` | string | Raw IPv6 address when the current caller IP is IPv6. | | `isCurrentIpv6` | boolean | `true` when the incoming request IP is IPv6. | | `userAgent` | string | Caller user agent string. | | `acceptLanguage` | string | Caller `Accept-Language` header when present. | | `city` | string | Detected city. | | `country` | string | Detected country code. | | `continent` | string | Detected continent code. | | `latitude` | string | Latitude associated with the caller IP. | | `longitude` | string | Longitude associated with the caller IP. | | `region` | string | Detected region or state name. | | `regionCode` | string | Region code. | | `timezone` | string | Detected IANA timezone. | | `postalCode` | string | Detected postal code. | **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | -------------------- | | `200` | IP data returned | | `401` | Invalid token | | `402` | Insufficient credits | | `405` | Method not allowed | # OpenAPI Specification > Download and use the echoValue OpenAPI 3.0 specification The echoValue API is fully documented using the **OpenAPI 3.0** specification format. ## Download [Section titled “Download”](#download) Download the latest spec here: [openapi.yaml](/openapi.yaml) ## View & Test Interactively [Section titled “View & Test Interactively”](#view--test-interactively) [Open in Swagger UI](https://petstore.swagger.io/?url=https://docs.echovalue.dev/openapi.yaml) ## Use Cases [Section titled “Use Cases”](#use-cases) ### Generate Client SDKs [Section titled “Generate Client SDKs”](#generate-client-sdks) Use [OpenAPI Generator](https://openapi-generator.tech/) to generate client libraries: ```sh openapi-generator-cli generate \ -i https://docs.echovalue.dev/openapi.yaml \ -g javascript \ -o ./echovalue-client ``` Supported languages include Java, Ruby, PHP, C#, TypeScript, Rust, and [many more](https://openapi-generator.tech/docs/generators). ### Import into API Tools [Section titled “Import into API Tools”](#import-into-api-tools) | Tool | Steps | | ------------ | ---------------------------------------------- | | **Postman** | File → Import → Link → Paste OpenAPI URL | | **Insomnia** | Create → Import From → URL → Paste OpenAPI URL | | **Paw** | File → Import → Paste OpenAPI URL | | **HTTPie** | Import → OpenAPI → Paste OpenAPI URL | ### API Validation in Tests [Section titled “API Validation in Tests”](#api-validation-in-tests) ```js // Example with jest-openapi const jestOpenAPI = require('jest-openapi'); jestOpenAPI('https://docs.echovalue.dev/openapi.yaml'); test('GET /default/mykey returns valid response', async () => { const response = await fetch('https://api.echovalue.dev/kv/default/mykey', { headers: { 'x-token': 'mytoken' } }); expect(response).toSatisfyApiSpec(); }); ``` ### Mock Server [Section titled “Mock Server”](#mock-server) ```sh npx @stoplight/prism-cli mock \ https://docs.echovalue.dev/openapi.yaml # Runs at http://127.0.0.1:4010 ``` ## Notes [Section titled “Notes”](#notes) * The OpenAPI specification is updated alongside documentation releases. * Always use `https://docs.echovalue.dev/openapi.yaml` for the latest published version. # Response Headers > Standard response headers returned by the echoValue API The OpenAPI specification defines these headers on wallet-consuming endpoints: | Header | Type | Description | | ----------- | ------- | --------------------------------------------------- | | `x-cost` | integer | Credits deducted from your wallet for this request | | `x-balance` | integer | Credits remaining in your wallet after this request | ## Response Headers [Section titled “Response Headers”](#response-headers) These headers appear on wallet-consuming endpoints such as key-value operations, webhook management endpoints, token balance/settings endpoints, and other endpoints that explicitly emit them in the OpenAPI file. `GET /token/logs` remains free, but the current OpenAPI file declares `x-cost` on its `200` response. ## Examples [Section titled “Examples”](#examples) * cURL ```sh # Use -i to show response headers curl -i 'https://api.echovalue.dev/kv/default/mykey' \ -H 'x-token: mytoken' # Output includes: # x-cost: 1 # x-balance: 99 ``` * JavaScript ```js fetch('https://api.echovalue.dev/kv/default/mykey', { headers: { 'x-token': 'mytoken' } }) .then(response => { console.log('Cost:', response.headers.get('x-cost')); console.log('Balance:', response.headers.get('x-balance')); return response.text(); }); ``` * Python ```python import requests response = requests.get('https://api.echovalue.dev/kv/default/mykey', headers={'x-token': 'mytoken'} ) print('Cost:', response.headers.get('x-cost')) print('Balance:', response.headers.get('x-balance')) ``` * PHP ```php ``` * Go ```go package main import ( "fmt" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://api.echovalue.dev/kv/default/mykey", nil) req.Header.Set("x-token", "mytoken") client := &http.Client{} resp, _ := client.Do(req) defer resp.Body.Close() fmt.Println("Cost:", resp.Header.Get("x-cost")) fmt.Println("Balance:", resp.Header.Get("x-balance")) } ``` # Token Management > Overview of token creation, wallet balance, recharge, and low-balance notification endpoints Token management covers token creation, balance lookup, wallet recharge, and low-balance notifications. ## Endpoints [Section titled “Endpoints”](#endpoints) | Page | Endpoint | | ------------------------------------------------------ | --------------------- | | [Create Token](/token/create/) | `POST /token` | | [Get Balance](/token/balance/) | `GET /token` | | [Recharge Wallet](/token/recharge/) | `GET /recharge` | | [Get Low Balance Settings](/token/settings/) | `GET /token/settings` | | [Update Low Balance Settings](/token/update-settings/) | `PUT /token/settings` | Caution Tokens unused for two years are automatically deactivated and all associated data is deleted. ## Pricing [Section titled “Pricing”](#pricing) | Operation | Cost | | --------------------------- | -------- | | Create token | Free | | Get wallet balance | 1 credit | | Get recharge link | Free | | Get low balance settings | 1 credit | | Update low balance settings | 1 credit | ## Notes [Section titled “Notes”](#notes) * For ongoing balance monitoring, prefer the `x-balance` response header on paid endpoints. * Low-balance notifications can target either an email address or a webhook. * Webhook configuration for low-balance alerts uses the same format family as the inbound email webhook flow. See [Webhook Formats](/webhook/formats/slack/). # Get Balance > Retrieve wallet balance and token metadata `GET https://api.echovalue.dev/token` Returns remaining credits and token metadata. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | ## Response [Section titled “Response”](#response) `200 OK` — JSON object. ```json { "wallet": 12345, "created": "2023-08-09T15:40:09.77Z", "hash": "a1b2c3d4e5f6..." } ``` | Field | Type | Description | | --------- | ----------------- | ------------------------------- | | `wallet` | integer | Remaining credit balance | | `created` | string (ISO 8601) | Date when the token was created | | `hash` | string | SHA256 hash of your token | **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | -------------------- | | `200` | Balance returned | | `401` | Invalid token | | `402` | Insufficient credits | ## Notes [Section titled “Notes”](#notes) * `hash` is the SHA256 hash of the wallet token and can be used for identification in downstream payloads. ## Examples [Section titled “Examples”](#examples) ```sh curl 'https://api.echovalue.dev/token' \ -H 'x-token: mytoken' ``` # Create Token > Create a new echoValue API token `POST https://api.echovalue.dev/token` Creates a new API token. No authentication is required. ## Request [Section titled “Request”](#request) **Body** (`application/x-www-form-urlencoded`): | Parameter | Value | Description | | --------- | ----- | -------------------------------- | | `token` | `new` | Must be the literal string `new` | ## Response [Section titled “Response”](#response) `200 OK` — the new token as plain text. ```plaintext a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 ``` ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ------------------------------------ | | `200` | Token created | | `400` | Missing or invalid `token` parameter | ## Examples [Section titled “Examples”](#examples) ```sh curl 'https://api.echovalue.dev/token' \ -d 'token=new' ``` Caution Tokens unused for two years are automatically deactivated and all associated data is deleted. # Recharge Wallet > Get a payment link to add credits to your wallet `GET https://api.echovalue.dev/recharge` Returns a payment link to add credits to your wallet. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | **Query parameters:** | Parameter | Type | Description | Default | | --------- | ------ | ------------------------------------------- | ------- | | `amount` | string | Recharge tier. Accepted values: `1` or `3`. | `1` | ## Response [Section titled “Response”](#response) `200 OK` — payment URL as plain text. ```plaintext https://buy.stripe.com/?client_reference_id= ``` **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed (always 0) | ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ---------------------- | | `200` | Recharge link returned | | `401` | Invalid token | | `405` | Method not allowed | ## Examples [Section titled “Examples”](#examples) ```sh curl 'https://api.echovalue.dev/recharge?amount=1' \ -H 'x-token: mytoken' ``` Follow the returned URL to complete the payment. The returned payment URL includes the wallet hash in `client_reference_id`. # Get Low Balance Settings > Retrieve the current low-balance notification settings `GET https://api.echovalue.dev/token/settings` Returns the current low-balance notification settings. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | ## Response [Section titled “Response”](#response) `200 OK` — JSON object. ```json { "low_balance_threshold": 10, "low_balance_webhook": { "url": "https://yourdomain.com/alert", "headers": {}, "format": "slack" }, "low_balance_email": "alerts@example.com" } ``` | Field | Type | Description | | ----------------------- | ------- | -------------------------------------------------------------------------- | | `low_balance_threshold` | integer | Balance threshold that triggers notifications (`0` disables notifications) | | `low_balance_webhook` | object | Webhook configuration | | `low_balance_email` | string | Email address for notifications | If `low_balance_webhook` is present, it follows the same format family used by mailbox webhooks: `format`, `template`, and `options` are optional. **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | -------------------- | | `200` | Settings returned | | `401` | Invalid token | | `402` | Insufficient credits | ## Notes [Section titled “Notes”](#notes) * Set `low_balance_threshold` to `0` to disable low-balance notifications. * Notification delivery can target an email address, a webhook, or both. ## Examples [Section titled “Examples”](#examples) ```sh curl 'https://api.echovalue.dev/token/settings' \ -H 'x-token: mytoken' ``` # Update Low Balance Settings > Configure low-balance notifications for your wallet `PUT https://api.echovalue.dev/token/settings` Configures low-balance notifications. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | -------------- | -------------------------- | | `x-token` | Your API token | | `Content-Type` | Must be `application/json` | **Body** (`application/json`): | Field | Type | Description | Required | | ----------------------- | ------- | --------------------------------------------------------------------- | -------- | | `low_balance_threshold` | integer | Balance threshold that triggers notifications. Set to `0` to disable. | No | | `low_balance_webhook` | object | Webhook configuration | No | | `low_balance_email` | string | Email address to receive notifications | No | **Webhook configuration object:** | Field | Type | Description | Required | | ---------- | ------ | -------------------------------------------------------------------------------------------------------------- | -------- | | `url` | string | HTTPS URL to receive the notification | Yes | | `headers` | object | Custom headers to include in the webhook request | No | | `format` | string | Native payload format: `slack`, `discord`, `teams`, `telegram`, `pagerduty`, `custom` | No | | `template` | object | JSON template when `format` is `custom`. Supports `{{wallet}}`, `{{threshold}}`, `{{token_hash}}` placeholders | No | | `options` | object | Format-specific options such as `chat_id` or `routing_key` | No | Notifications are sent at most once per low-balance incident. The cooldown resets after the balance rises back above the threshold. ## Response [Section titled “Response”](#response) `200 OK` ```plaintext OK ``` **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ---------------------------------------- | | `200` | Settings updated | | `400` | Invalid webhook URL or invalid threshold | | `401` | Invalid token | | `402` | Insufficient credits | ## Notes [Section titled “Notes”](#notes) * Set `low_balance_threshold` to `0` to disable notifications. * `low_balance_webhook.url` must be HTTPS. ## Examples [Section titled “Examples”](#examples) ```sh curl 'https://api.echovalue.dev/token/settings' \ -X PUT \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "low_balance_threshold": 10, "low_balance_webhook": { "url": "https://yourdomain.com/alert", "format": "slack" }, "low_balance_email": "alerts@example.com" }' ``` # URL To Metadata > Fetch URL metadata and optional summaries through the echoValue URL To Metadata API `POST https://api.echovalue.dev/url-to-metadata` Fetches metadata for a public URL through the echoValue URL To Metadata service. The request is authenticated with `x-token`, charged by echoValue, and logged. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | -------------- | -------------------------- | | `x-token` | Your API token | | `content-type` | Must be `application/json` | **Body:** | Field | Type | Required | Description | | ----------- | ------ | -------- | -------------------------------------------------------------------------------- | | `url` | string | Yes | Public URL to analyze | | `aiSummary` | string | No | AI enrichment level: `none`, `short`, `medium`, or `long`. `none` is the default | **Examples:** ```sh curl 'https://api.echovalue.dev/url-to-metadata' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://example.com/article", "aiSummary": "short" }' ``` ## Response [Section titled “Response”](#response) `200 OK` returns the JSON payload produced by the URL To Metadata service. ```json { "success": true, "data": { "seo": { "title": "Example Company - Leading SaaS Platform", "description": "Enterprise software for team collaboration", "keywords": ["saas", "collaboration", "enterprise"], "canonical": "https://example.com" }, "technical": { "statusCode": 200, "finalUrl": "https://example.com", "originalUrl": "https://example.com/article", "robotsAllowed": true, "isSecure": true, "contentType": "text/html; charset=utf-8" }, "links": { "internal": { "total": 12, "urls": ["/about", "/pricing", "/contact"] }, "external": { "total": 2, "urls": ["https://github.com/example"], "domains": ["github.com"] } }, "ai": { "summary": { "short": "Example Company provides cloud-based collaboration software for enterprises.", "contentLength": 5240, "truncated": false }, "keyFacts": { "companyName": "Example Company Inc.", "industry": "Enterprise Software", "businessModel": "Subscription" }, "processingTime": 2340 } }, "timestamp": "2025-06-19T15:54:01.821Z" } ``` The response shape is partially flexible. Top-level fields such as `success`, `data`, and `timestamp` are stable, while nested keys inside `data` can vary depending on the target page and whether AI enrichment is enabled. ## Response Shape [Section titled “Response Shape”](#response-shape) | Field | Type | Description | | ----------- | ----------------- | ----------------------------------------------------------------------------------------------------- | | `success` | boolean | Indicates whether the scrape completed successfully | | `data` | object | Extracted metadata grouped by area such as `seo`, `technical`, `links`, `social`, `contact`, and `ai` | | `timestamp` | string (ISO 8601) | When the response was produced | When `aiSummary` is `none`, the `ai` block may be omitted. When AI enrichment is enabled, `data.ai.summary`, `data.ai.keyFacts`, and `data.ai.processingTime` may be returned. **Response headers:** | Header | Description | | ----------- | --------------------------------- | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed for this request | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ---------------------------------------- | | `200` | Metadata returned successfully | | `400` | Invalid request body or validation error | | `401` | Invalid token | | `402` | Wallet has insufficient credits | | `405` | Method not allowed | | `500` | Internal service error | | `502` | Metadata service unavailable | | `503` | Metadata service unavailable | ## Notes [Section titled “Notes”](#notes) * The target URL must be publicly reachable by the metadata service. * Treat nested fields inside `data` as optional and schema-light. Parse defensively. * This endpoint is best suited for metadata extraction, link analysis, and optional summary generation over public pages. # Versioning / Changelog > How echoValue communicates API changes and documentation updates echoValue keeps the public API contract documented in the [OpenAPI Specification](/openapi/) and endpoint reference pages. ## Versioning [Section titled “Versioning”](#versioning) The API uses the stable base URL: ```txt https://api.echovalue.dev ``` Changes are documented in place unless a future breaking change requires a new versioned surface. Client integrations should rely on documented request fields, response fields, status codes, limits, and response headers. ## Compatibility [Section titled “Compatibility”](#compatibility) Non-breaking changes can include: * New optional response fields * New optional request fields * New endpoints * Clarifications to limits, pricing notes, or examples Breaking changes include removals, renamed fields, stricter required fields, changed status-code meaning, or incompatible behavior changes for existing endpoints. ## Changelog [Section titled “Changelog”](#changelog) | Date | Change | | ---------- | --------------------------------------------------- | | 2026-04-24 | Added this versioning and changelog reference page. | ## Related [Section titled “Related”](#related) * [API Cheat Sheet](/api-cheat-sheet/) * [Limits & Retention](/limits-retention/) * [Response Headers](/response-headers/) * [Errors](/errors/) * [OpenAPI Specification](/openapi/) # Webhook > Configure inbound email forwarding or scheduled webhook deliveries The webhook API has two mutually exclusive modes: 1. **Inbound Email Webhook** echoValue assigns an opaque mailbox address in the form `@hook.echovalue.dev`. Every received email is forwarded to your webhook URL. 2. **Scheduled Webhook** echoValue calls your webhook URL on a recurring schedule defined by a 5-field cron expression and an IANA timezone. Each webhook is managed through a public `webhookId`. If you omit it when creating a webhook, echoValue uses `default`. ## Tools [Section titled “Tools”](#tools) * Use [crontab.echovalue.dev](https://crontab.echovalue.dev) to generate the 5-field cron expression for `schedule.cron`. * Use [webhook.echovalue.dev](https://webhook.echovalue.dev) to create a temporary HTTPS webhook URL for testing. ## Pick The Right Mode [Section titled “Pick The Right Mode”](#pick-the-right-mode) | Need | Mode | What you send | What you get back | Payload | | ----------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------- | ------------------------------------- | --------------------------------------------------- | | Turn inbound email into HTTP calls | Inbound Email Webhook | `url` plus optional `format`, `template`, `options`, `includeAttachments` | An opaque `email` address | Inbound email payload or transformed format payload | | Run recurring HTTP callbacks | Scheduled Webhook | `url` plus `schedule` | Schedule metadata such as `nextRunAt` | Scheduled-run payload | | Verify delivery manually | Test Webhook | Existing `webhookId` | Delivery result with `success` | Test payload | | Send to Slack, Discord, Teams, Telegram, or PagerDuty | Inbound Email Webhook with `format` | Platform-specific `format` and `options` | An opaque `email` address | Platform-native payload | Caution A webhook is either mailbox-based or scheduled, never both. Do not send `schedule` together with mailbox-only fields such as `format`, `template`, `options`, or `includeAttachments`. ## Endpoint Reference [Section titled “Endpoint Reference”](#endpoint-reference) | Page | Endpoint | | ------------------------------------- | ----------------------------------------------------------- | | [Create Webhook](/webhook/create/) | `POST /webhook` | | [List Webhooks](/webhook/list/) | `GET /webhook` | | [Get Webhook](/webhook/get/) | `GET /webhook/` | | [Update Webhook](/webhook/update/) | `PUT /webhook/` and `PATCH /webhook/` | | [Delete Webhook](/webhook/delete/) | `DELETE /webhook/` | | [Test Webhook](/webhook/test/) | `POST /webhook//test` | | [Webhook Payloads](/webhook/payload/) | Delivery payload schemas | ## Guides [Section titled “Guides”](#guides) | Guide | When to use it | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | [Webhook Inbound Email](/guides/webhook-inbound-email/) | You want an email address that forwards inbound email to Slack, Discord, PagerDuty, or your own endpoint | | [Webhook Scheduled Jobs](/guides/webhook-scheduled-jobs/) | You want cron-style webhook execution without any mailbox address | ## Format Adapters [Section titled “Format Adapters”](#format-adapters) Format adapters apply only to the inbound email mode. | Format | Platform | | ----------- | ------------------------------------------------------ | | `slack` | [Slack Incoming Webhooks](/webhook/formats/slack/) | | `discord` | [Discord Webhooks](/webhook/formats/discord/) | | `teams` | [Microsoft Teams](/webhook/formats/teams/) | | `telegram` | [Telegram Bot API](/webhook/formats/telegram/) | | `pagerduty` | [PagerDuty Events API v2](/webhook/formats/pagerduty/) | | `custom` | [Custom JSON template](/webhook/formats/custom/) | Without a `format`, the raw inbound email payload is forwarded as-is. Scheduled webhooks always deliver the scheduled-run JSON payload. ## Pricing [Section titled “Pricing”](#pricing) | Operation | Cost | | ---------------------------------- | --------- | | Configure webhook | 1 credit | | Get webhook config | 1 credit | | Delete webhook | 1 credit | | Test webhook | 1 credit | | Scheduled cron run | 2 credits | | Email processed (no attachments) | 2 credits | | Email processed (with attachments) | 5 credits | # Create Webhook > Create either an inbound email webhook or a scheduled webhook `POST https://api.echovalue.dev/webhook` Creates a new webhook. ## Headers [Section titled “Headers”](#headers) | Header | Description | | -------------- | -------------------------- | | `x-token` | Your API token | | `Content-Type` | Must be `application/json` | ## Request [Section titled “Request”](#request) ## Common Fields [Section titled “Common Fields”](#common-fields) These fields are valid in both modes. | Field | Type | Description | Required | | ----------- | ------ | -------------------------------------------------------------------------------------------- | -------- | | `webhookId` | string | Public webhook identifier used for management endpoints. Defaults to `default` when omitted. | No | | `url` | string | Callback URL; must be HTTPS and publicly reachable. | Yes | | `headers` | object | Custom headers to include in the callback request. | No | ## Inbound Email Webhook [Section titled “Inbound Email Webhook”](#inbound-email-webhook) Use this mode when you want echoValue to assign an opaque mailbox address and forward each inbound email to your webhook URL. ### Mailbox-only fields [Section titled “Mailbox-only fields”](#mailbox-only-fields) | Field | Type | Description | Required | | -------------------- | ------- | ------------------------------------------------------------------------------------- | -------- | | `format` | string | Native message format: `slack`, `discord`, `teams`, `telegram`, `pagerduty`, `custom` | No | | `template` | object | JSON template for `custom` format. Max 2048 bytes, 5 nesting levels. | No | | `options` | object | Format-specific options such as `chat_id` or `routing_key` | No | | `includeAttachments` | boolean | Include email attachments in payloads. Default: `true`. | No | Need a temporary callback URL while testing? Use [webhook.echovalue.dev](https://webhook.echovalue.dev) and set the generated HTTPS URL as `url`. ### Minimal request [Section titled “Minimal request”](#minimal-request) ```json { "webhookId": "support-alerts", "url": "https://hooks.slack.com/services/XXX/YYY/ZZZ", "format": "slack", "includeAttachments": false } ``` ### Typical response [Section titled “Typical response”](#typical-response) ```json { "webhookId": "support-alerts", "webhook": "https://hooks.slack.com/services/XXX/YYY/ZZZ", "headers": {}, "format": "slack", "options": {}, "includeAttachments": false, "email": "r_abcd1234@hook.echovalue.dev", "hash": "a1b2c3d4e5f6..." } ``` ### Notes [Section titled “Notes”](#notes) * The response includes an opaque `email` mailbox address. * `format`, `template`, `options`, and `includeAttachments` are only valid in this mode. * Inbound email payloads are limited to 1 MiB after parsing. * See [Webhook Inbound Email](/guides/webhook-inbound-email/) for end-to-end examples. ## Scheduled Webhook [Section titled “Scheduled Webhook”](#scheduled-webhook) Use this mode when you want echoValue to call your webhook URL on a recurring schedule. ### Schedule field [Section titled “Schedule field”](#schedule-field) | Field | Type | Description | Required | | ------------------- | ------- | ----------------------------------------------------------------------------- | -------- | | `schedule.cron` | string | 5-field cron expression: `minute hour day month weekday` | Yes | | `schedule.timezone` | string | IANA timezone used to evaluate the cron expression, for example `Europe/Rome` | Yes | | `schedule.active` | boolean | Whether scheduled delivery is enabled. Default: `true`. | No | Use [crontab.echovalue.dev](https://crontab.echovalue.dev) to generate a valid value for `schedule.cron`. ### Minimal request [Section titled “Minimal request”](#minimal-request-1) ```json { "webhookId": "daily-sync", "url": "https://example.com/hooks/daily-sync", "schedule": { "cron": "0 9 * * *", "timezone": "Europe/Rome" } } ``` ### Typical response [Section titled “Typical response”](#typical-response-1) ```json { "webhookId": "daily-sync", "webhook": "https://example.com/hooks/daily-sync", "headers": {}, "schedule": { "cron": "0 9 * * *", "timezone": "Europe/Rome", "active": true, "nextRunAt": "2026-04-24T07:00:00.000Z" }, "hash": "a1b2c3d4e5f6..." } ``` ### Notes [Section titled “Notes”](#notes-1) * The response does not include an `email` field. * Do not send `format`, `template`, `options`, or `includeAttachments` together with `schedule`. * See [Webhook Scheduled Jobs](/guides/webhook-scheduled-jobs/) for end-to-end examples. Caution For scheduled webhooks, the first run must be within 30 days from creation and the interval between runs must never exceed 30 days. ## Response Fields [Section titled “Response Fields”](#response-fields) | Field | Type | Description | | -------------------- | ------- | ---------------------------------------------------------------- | | `webhookId` | string | Public webhook identifier for management operations | | `webhook` | string | Configured callback URL | | `headers` | object | Custom headers | | `format` | string | Native format, only returned for inbound email webhooks | | `template` | object | Custom template, only returned when `format` is `custom` | | `options` | object | Format options; sensitive values are redacted | | `includeAttachments` | boolean | Returned only for inbound email webhooks | | `schedule` | object | Returned only for scheduled webhooks | | `email` | string | Opaque mailbox address, returned only for inbound email webhooks | | `hash` | string | SHA256 hash of your token | ## Notes [Section titled “Notes”](#notes-2) * A webhook is either mailbox-triggered or scheduled, never both. * The webhook URL must be HTTPS and publicly reachable. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ------------------------------------------------------------ | | `200` | Webhook created | | `400` | Invalid URL, invalid field combination, or malformed request | | `401` | Invalid token | | `402` | Insufficient credits | | `409` | `webhookId` already exists | | `500` | Internal server error | Caution Localhost and private IP addresses are not accepted. # Delete Webhook > Remove the webhook configuration from your echoValue wallet `DELETE https://api.echovalue.dev/webhook/` Removes a specific webhook from your wallet using its public `webhookId`. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | **Path parameters:** | Parameter | Type | Description | Required | | ----------- | ------ | ----------------------------------- | -------- | | `webhookId` | string | Public webhook identifier to delete | Yes | ```sh curl 'https://api.echovalue.dev/webhook/slack' \ -H 'x-token: mytoken' \ -X DELETE ``` ## Response [Section titled “Response”](#response) `200 OK` ```plaintext OK ``` **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | -------------------- | | `200` | Webhook deleted | | `401` | Invalid token | | `402` | Insufficient credits | | `404` | Webhook not found | # Webhook Format: Custom > Create custom inbound email payload transformations using echoValue's template engine When `format` is set to `custom`, you define your own JSON template with placeholders. echoValue fills the values from the incoming email and sends the result to your webhook URL. ## Configure in echoValue [Section titled “Configure in echoValue”](#configure-in-echovalue) ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://yourdomain.com/webhook", "format": "custom", "template": { "subject": "{{subject}}", "from": "{{from.name}} <{{from.email}}>", "body": "{{text|html}}", "attachments": "{{attachments[0].filename}}" } }' ``` ## Template Placeholders [Section titled “Template Placeholders”](#template-placeholders) Placeholders use `{{dot.notation}}` syntax and are resolved against the [webhook payload](/webhook/payload/). ### Dot Notation [Section titled “Dot Notation”](#dot-notation) ```plaintext {{subject}} {{from.name}} {{from.email}} {{attachments[0].filename}} ``` ### Array Index [Section titled “Array Index”](#array-index) ```plaintext {{attachments[0].filename}} {{attachments[-1].filename}} ``` ### Fallback Chain [Section titled “Fallback Chain”](#fallback-chain) ```plaintext {{text|html}} {{from.name|from.email}} ``` ### Literal Fallback [Section titled “Literal Fallback”](#literal-fallback) ```plaintext {{from.name|"unknown"}} {{text|"No body"}} ``` ## Example Templates [Section titled “Example Templates”](#example-templates) ```json { "template": { "text": "*{{subject}}*\nFrom: {{from.name}} <{{from.email}}>\n\n{{text|html}}" } } ``` ## Limits [Section titled “Limits”](#limits) | Constraint | Value | | ----------------- | ---------- | | Max template size | 2048 bytes | | Max nesting depth | 5 levels | # Webhook Format: Discord > Forward inbound email to Discord using echoValue's native Discord formatting When `format` is set to `discord`, incoming emails are converted into Discord messages using the [Webhook API](https://discord.com/developers/docs/resources/webhook). ## Get the Webhook URL [Section titled “Get the Webhook URL”](#get-the-webhook-url) 1. In Discord, right-click the channel and select **Edit Channel**. 2. Click **Integrations** → **Webhooks** → **New Webhook**. 3. Copy the generated webhook URL: ```plaintext https://discord.com/api/webhooks/000000000000000000/XXXXXXXXXXXXXXXXXXXXXXXXXXXX ``` ## Configure in echoValue [Section titled “Configure in echoValue”](#configure-in-echovalue) ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://discord.com/api/webhooks/000000000000000000/XXXX", "format": "discord" }' ``` ## Output Preview [Section titled “Output Preview”](#output-preview) ```plaintext **Subject line** From: Sender Name Email body text ``` # Webhook Format: PagerDuty > Forward inbound email to PagerDuty using echoValue to trigger incidents automatically When `format` is set to `pagerduty`, incoming emails trigger PagerDuty incidents via the [Events API v2](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview). This format requires a `routing_key` option. ## Get the Routing Key [Section titled “Get the Routing Key”](#get-the-routing-key) 1. In PagerDuty, go to **Services** and select the service you want to trigger. 2. Open **Integrations** and add **Events API v2**. 3. Copy the integration key. ## Configure in echoValue [Section titled “Configure in echoValue”](#configure-in-echovalue) ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://events.pagerduty.com/v2/enqueue", "format": "pagerduty", "options": { "routing_key": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" } }' ``` ## Output Mapping [Section titled “Output Mapping”](#output-mapping) | PagerDuty field | Value | | -------------------------- | --------------------- | | `summary` | Email subject | | `source` | Sender email address | | `severity` | `info` | | `custom_details.text` | Plain text email body | | `custom_details.from_name` | Sender display name | # Webhook Format: Slack > Forward inbound email to Slack using echoValue's native Slack formatting When `format` is set to `slack`, incoming emails are converted into Slack messages using the [Incoming Webhooks](https://api.slack.com/messaging/webhooks) API. ## Get the Webhook URL [Section titled “Get the Webhook URL”](#get-the-webhook-url) 1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App** → **From scratch**. 2. In the app settings sidebar, click **Incoming Webhooks** and toggle it **On**. 3. Click **Add New Webhook to Workspace**, select the target channel, then click **Allow**. 4. Copy the generated URL: ```plaintext https://hooks.slack.com/services/T00000000/B00000000/YOUR_WEBHOOK_PATH ``` ## Configure in echoValue [Section titled “Configure in echoValue”](#configure-in-echovalue) ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://hooks.slack.com/services/T00000000/B00000000/YOUR_WEBHOOK_PATH", "format": "slack" }' ``` ## Output Preview [Section titled “Output Preview”](#output-preview) ```plaintext *Subject line* From: Sender Name Email body text ``` # Webhook Format: Microsoft Teams > Forward inbound email to Microsoft Teams using echoValue's native Teams formatting When `format` is set to `teams`, incoming emails are converted into Microsoft Teams messages using the incoming webhook format. ## Get the Webhook URL [Section titled “Get the Webhook URL”](#get-the-webhook-url) ### Via Workflows [Section titled “Via Workflows”](#via-workflows) 1. In the channel, click **+** next to the message input → **Workflows**. 2. Select **Post to a channel when a webhook request is received**. 3. Finish setup and copy the generated URL. ### Via Connectors [Section titled “Via Connectors”](#via-connectors) 1. In the channel, click **···** → **Connectors**. 2. Search for **Incoming Webhook**, click **Add** → **Configure**. 3. Give it a name and copy the generated URL. ## Configure in echoValue [Section titled “Configure in echoValue”](#configure-in-echovalue) ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://prod-00.westeurope.logic.azure.com:443/workflows/XXXX/...", "format": "teams" }' ``` ## Output Preview [Section titled “Output Preview”](#output-preview) ```plaintext **Subject line** From: Sender Name Email body text ``` # Webhook Format: Telegram > Forward inbound email to Telegram using echoValue's native Telegram formatting When `format` is set to `telegram`, incoming emails are sent to a Telegram chat via the [Bot API](https://core.telegram.org/bots/api#sendmessage). This format requires a bot token in the URL and a `chat_id` option. ## Get the Bot Token and Chat ID [Section titled “Get the Bot Token and Chat ID”](#get-the-bot-token-and-chat-id) ### Create a Bot [Section titled “Create a Bot”](#create-a-bot) 1. Open Telegram and search for **@BotFather**. 2. Send `/newbot` and follow the instructions. 3. Copy the bot token: ```plaintext 1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi ``` ### Find the Chat ID [Section titled “Find the Chat ID”](#find-the-chat-id) 1. Send any message to your bot. 2. Open `https://api.telegram.org/bot{TOKEN}/getUpdates`. 3. Find the `chat.id` value in the response. ## Configure in echoValue [Section titled “Configure in echoValue”](#configure-in-echovalue) ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' \ -H 'Content-Type: application/json' \ -d '{ "url": "https://api.telegram.org/bot1234567890:ABCDEFGHIJKLMN/sendMessage", "format": "telegram", "options": { "chat_id": "123456789" } }' ``` ## Output Preview [Section titled “Output Preview”](#output-preview) ```plaintext Subject line From: Sender Name Email body text ``` # Get Webhook > Retrieve a single webhook configuration by webhookId `GET https://api.echovalue.dev/webhook/` Returns a single webhook configuration. Responses differ slightly for inbound email and scheduled webhooks. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | **Path parameters:** | Parameter | Type | Description | Required | | ----------- | ------ | ------------------------------------- | -------- | | `webhookId` | string | Public webhook identifier to retrieve | Yes | ```sh curl 'https://api.echovalue.dev/webhook/slack' \ -H 'x-token: mytoken' ``` ## Single Webhook Response [Section titled “Single Webhook Response”](#single-webhook-response) `200 OK` — JSON object. ```json { "webhookId": "daily-sync", "webhook": "https://example.com/hooks/daily-sync", "headers": {}, "schedule": { "cron": "0 9 * * *", "timezone": "Europe/Rome", "active": true, "nextRunAt": "2026-04-24T07:00:00.000Z", "lastRunAt": "2026-04-23T07:00:00.000Z", "lastStatus": "success" }, "hash": "a1b2c3d4e5f6..." } ``` | Field | Type | Description | | -------------------- | ------- | ---------------------------------------------------------------------------------------------- | | `webhookId` | string | Public webhook identifier used for update/delete/test operations | | `webhook` | string | Your configured callback URL | | `headers` | object | Custom headers (values are redacted) | | `format` | string | Configured native format, if any (e.g. `slack`, `telegram`, `custom`) | | `template` | object | Custom template object (only present when format is `custom`) | | `options` | object | Format-specific options (sensitive values like `routing_key` are redacted as `***`) | | `includeAttachments` | boolean | Whether attachments are included in webhook payload. Returned only for inbound email webhooks. | | `schedule` | object | Cron schedule configuration and runtime status for scheduled webhooks | | `email` | string | Opaque mailbox address that triggers this webhook. Returned only for inbound email webhooks. | | `hash` | string | SHA256 hash of your token — use this to identify incoming payloads | ## Interpreting The Response [Section titled “Interpreting The Response”](#interpreting-the-response) * `email` present: this webhook is waiting for inbound email. * `schedule` present: this webhook runs on a schedule. * `format` present: inbound email is transformed into a provider-specific payload before delivery. * Scheduled webhook responses omit mailbox-only fields such as `format`, `template`, `options`, and `includeAttachments`. **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | -------------------- | | `200` | Webhook returned | | `400` | Invalid request | | `401` | Invalid token | | `402` | Insufficient credits | | `404` | Webhook not found | # List Webhooks > List all webhook configurations for your wallet `GET https://api.echovalue.dev/webhook` Returns all configured webhooks for your wallet, including inbound email webhooks and scheduled webhooks. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | ## Response [Section titled “Response”](#response) `200 OK` — JSON object. ```json { "webhooks": [ { "webhookId": "inbox-alerts", "webhook": "https://example.com/hooks/inbox-alerts", "headers": {}, "format": "slack", "options": {}, "includeAttachments": true, "email": "r_abcd1234@hook.echovalue.dev", "hash": "a1b2c3d4e5f6..." }, { "webhookId": "daily-sync", "webhook": "https://example.com/hooks/daily-sync", "headers": {}, "schedule": { "cron": "0 9 * * *", "timezone": "Europe/Rome", "active": true, "nextRunAt": "2026-04-24T07:00:00.000Z", "lastRunAt": "2026-04-23T07:00:00.000Z", "lastStatus": "success" }, "hash": "a1b2c3d4e5f6..." } ] } ``` | Field | Type | Description | | ---------- | ----- | ------------------------------- | | `webhooks` | array | Array of webhook configurations | **Webhook entry fields:** | Field | Type | Description | | -------------------- | ------- | ----------------------------------------------------------------------------------------------- | | `webhookId` | string | Public webhook identifier used for get/update/delete/test operations | | `webhook` | string | Configured callback URL | | `headers` | object | Custom headers | | `format` | string | Configured native format, if any | | `template` | object | Custom template object when `format` is `custom` | | `options` | object | Format-specific options; sensitive values are redacted | | `includeAttachments` | boolean | Whether attachments are included in webhook payloads. Returned only for inbound email webhooks. | | `schedule` | object | Scheduling metadata for scheduled webhooks | | `email` | string | Opaque mailbox address that triggers this webhook. Returned only for inbound email webhooks. | | `hash` | string | SHA256 hash of your token | ## Reading The Result [Section titled “Reading The Result”](#reading-the-result) * If an entry has `email`, it is an inbound email webhook. * If an entry has `schedule`, it is a scheduled webhook. * If an entry has `format`, the inbound email payload is transformed before delivery. * Scheduled entries do not include mailbox-only fields such as `format`, `template`, `options`, or `includeAttachments`. **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | --------------------------- | | `200` | Configuration list returned | | `401` | Invalid token | | `402` | Insufficient credits | ## Examples [Section titled “Examples”](#examples) ```sh curl 'https://api.echovalue.dev/webhook' \ -H 'x-token: mytoken' ``` # Webhook Payloads > JSON payload structures sent to your webhook for inbound email and scheduled deliveries Webhook payloads depend on the configured use case. ## Which Payload Do I Receive? [Section titled “Which Payload Do I Receive?”](#which-payload-do-i-receive) | Configuration | Payload sent to your endpoint | | -------------------------------------- | ------------------------------------- | | Inbound email webhook without `format` | Raw inbound email JSON payload | | Inbound email webhook with `format` | Provider-specific transformed payload | | Scheduled webhook with `schedule` | Scheduled-run JSON payload | ## Inbound Email Payload [Section titled “Inbound Email Payload”](#inbound-email-payload) When an email arrives at the opaque `email` address returned by the webhook API, echoValue sends a `POST` request to your webhook URL with this JSON body: ```json { "externalMessageId": "", "receivedAt": "2026-04-04T09:21:35.218Z", "from": { "email": "john@gmail.com", "name": "John Snow" }, "to": [ { "email": "r_abcd1234@hook.echovalue.dev", "name": "Support Inbox" } ], "cc": [{ "email": "jane@gmail.com", "name": "Jane Doe" }], "subject": "Alert: Server down", "text": "Server #3 is not responding", "html": "
Server #3 is not responding
", "headers": {}, "attachments": [], "provider": "mail", "url": "https://hook.echovalue.dev/r_abcd1234", "raw": { "messageId": "" } } ``` ## Fields [Section titled “Fields”](#fields) | Field | Type | Description | | ------------------- | ----------------- | -------------------------------------------------------- | | `externalMessageId` | string | Unique identifier for the email message | | `receivedAt` | string (ISO 8601) | When the email was received | | `from` | object | Sender: `email` and optional `name` | | `to` | array | Recipient list from the inbound email source | | `cc` | array | CC recipients: each item has `email` and optional `name` | | `subject` | string | Email subject line | | `text` | string | Plain text body | | `html` | string | HTML body (if present) | | `headers` | object | Raw email headers as key-value pairs | | `attachments` | array | Attachment metadata when available | | `url` | string | Mailbox URL when available | | `provider` | string | Inbound source identifier when available | | `raw` | object | Raw inbound payload details when available | The exact attachment object is source-dependent and may include additional fields. Caution Inbound email payloads are limited to 1 MiB after parsing, so large emails or attachment-heavy messages may be rejected or stripped before delivery depending on message size and the `includeAttachments` setting. ## Scheduled Webhook Payload [Section titled “Scheduled Webhook Payload”](#scheduled-webhook-payload) When a webhook is triggered by `schedule`, echoValue sends a scheduled-run payload instead of the inbound email payload: ```json { "type": "scheduled_webhook_run", "webhookId": "daily-sync", "scheduledFor": "2026-04-22T12:05:00Z", "executedAt": "2026-04-22T12:05:01Z", "walletHash": "a1b2c3d4e5f6..." } ``` | Field | Type | Description | | -------------- | ----------------- | ---------------------------------- | | `type` | string | Always `scheduled_webhook_run` | | `webhookId` | string | Public webhook identifier | | `scheduledFor` | string (ISO 8601) | Time this run was scheduled for | | `executedAt` | string (ISO 8601) | Time the webhook call actually ran | | `walletHash` | string | SHA256 hash of your token | # Test Webhook > Send a dummy payload to your configured webhook to verify it's working `POST https://api.echovalue.dev/webhook//test` Sends a dummy payload to the specified webhook URL to verify it is working correctly. ## Request [Section titled “Request”](#request) **Headers:** | Header | Description | | --------- | -------------- | | `x-token` | Your API token | **Path parameters:** | Parameter | Type | Description | Required | | ----------- | ------ | --------------------------------- | -------- | | `webhookId` | string | Public webhook identifier to test | Yes | ## Notes [Section titled “Notes”](#notes) * Inbound email webhook: a dummy inbound email payload is sent. * Scheduled webhook: a dummy scheduled-run payload is sent. * The call reports delivery success or failure in the JSON body. ```sh curl 'https://api.echovalue.dev/webhook/slack/test' \ -H 'x-token: mytoken' \ -X POST ``` ## Response [Section titled “Response”](#response) `200 OK` — JSON object. ```json { "success": true, "message": "test webhook delivered" } ``` On failure: ```json { "success": false, "message": "webhook call failed: context deadline exceeded (Client.Timeout exceeded while awaiting headers)" } ``` | Field | Type | Description | | --------- | ------- | --------------------------------------------------- | | `success` | boolean | Whether the test webhook was successfully delivered | | `message` | string | Confirmation or error message | **Response headers:** | Header | Description | | ----------- | ------------------------------ | | `x-balance` | Wallet balance after this call | | `x-cost` | Credits consumed | See [Response Headers](/response-headers/) for details. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | -------------------- | | `200` | Test call processed | | `401` | Invalid token | | `402` | Insufficient credits | | `405` | Method not POST | # Update Webhook > Replace or patch an inbound email webhook or a scheduled webhook `PUT https://api.echovalue.dev/webhook/` Replaces the full webhook document for the specified `webhookId`. `url` is required. `PATCH https://api.echovalue.dev/webhook/` Updates only the fields you provide. `webhookId` is a path parameter and is not allowed in the request body. ## Headers [Section titled “Headers”](#headers) | Header | Description | | -------------- | -------------------------- | | `x-token` | Your API token | | `Content-Type` | Must be `application/json` | ## Request [Section titled “Request”](#request) ## Replace With `PUT` [Section titled “Replace With PUT”](#replace-with-put) Use `PUT` when you want to send the full new configuration. ### Inbound Email Webhook [Section titled “Inbound Email Webhook”](#inbound-email-webhook) Replace the webhook with mailbox mode by sending mailbox-only fields and omitting `schedule`. ```json { "url": "https://hooks.slack.com/services/XXX/YYY/ZZZ", "format": "slack", "includeAttachments": false } ``` ### Scheduled Webhook [Section titled “Scheduled Webhook”](#scheduled-webhook) Replace the webhook with scheduled mode by sending `schedule` and omitting mailbox-only fields. ```json { "url": "https://example.com/hooks/daily-sync", "schedule": { "cron": "0 9 * * *", "timezone": "Europe/Rome", "active": true } } ``` ## Patch With `PATCH` [Section titled “Patch With PATCH”](#patch-with-patch) Use `PATCH` when you want to update only part of the configuration. ### Patch semantics [Section titled “Patch semantics”](#patch-semantics) | Field | Behavior | | -------------------- | ----------------------------------------------------------------------------------------------------- | | `url` | Replaced when provided | | `headers` | Replaces the full headers object. Use `{}` to clear it. | | `format` | Mailbox-only field. Replaced when provided. | | `template` | Mailbox-only field. Replaces the template. Use `null` to remove it. | | `options` | Mailbox-only field. Replaces the full options object. Use `{}` to clear it. | | `includeAttachments` | Mailbox-only field. Replaced when provided. | | `schedule` | Scheduled field. Updates only the provided schedule fields. Use `null` to remove scheduling entirely. | ### Patch an inbound email webhook [Section titled “Patch an inbound email webhook”](#patch-an-inbound-email-webhook) ```json { "headers": { "X-Source": "support-inbox" }, "includeAttachments": false } ``` ### Patch a scheduled webhook [Section titled “Patch a scheduled webhook”](#patch-a-scheduled-webhook) ```json { "schedule": { "active": false } } ``` ## Mode Rules [Section titled “Mode Rules”](#mode-rules) * Inbound email mode uses `format`, `template`, `options`, and `includeAttachments`. * Scheduled mode uses `schedule`. * Do not send mailbox-only fields together with a non-null `schedule` object in the same request. * Set `schedule: null` in `PATCH` to switch a scheduled webhook back to mailbox mode. Caution For scheduled webhooks, the next run must be within 30 days from the update and the interval between runs must never exceed 30 days. ## Response [Section titled “Response”](#response) Response body and response headers are the same as [Create Webhook](/webhook/create/). ## Notes [Section titled “Notes”](#notes) * Use `PUT` when you want to replace the full document. * Use `PATCH` when you want to update only specific fields. * `headers`, `options`, and `template` replace the full value when provided. ## Status Codes [Section titled “Status Codes”](#status-codes) | Status | Meaning | | ------ | ------------------------------------------------------------ | | `200` | Webhook updated | | `400` | Invalid URL, invalid field combination, or malformed request | | `401` | Invalid token | | `402` | Insufficient credits | | `404` | Webhook not found | | `500` | Internal server error | Caution Localhost and private IP addresses are not accepted.