> ## Documentation Index
> Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Making HTTP Requests from SQL

> How to call external HTTP APIs directly from MySQL queries using VillageSQL's vsql_http extension — GET, POST, and parsing JSON responses.

> [card] [VillageSQL is a drop-in replacement for MySQL with extensions.](https://villagesql.com/docs/en/8.4/stable/install-server.md)
>
> All examples on this page work on VillageSQL. Install Now →

MySQL has no built-in way to make network requests. If you want to call an HTTP API, you pull the data out of the database, make the request in application code, and push results back. VillageSQL's `vsql_http` extension adds `http_get()`, `http_post()`, and the full range of HTTP methods directly to SQL — so you can call external APIs from the same place the data lives.

## The Problem: Leaving the Database for Every API Call

A typical pattern without VillageSQL:

```python theme={null}
# Pull rows from MySQL
rows = db.query("SELECT id, tracking_number FROM shipments WHERE status IS NULL")

# Call a shipping API for each row
for row in rows:
    resp = requests.get(f"https://api.shipping.com/track/{row['tracking_number']}")
    status = resp.json()["status"]
    db.execute("UPDATE shipments SET status = ? WHERE id = ?", status, row["id"])
```

This works but every row is three round-trips: SELECT, API call, UPDATE. The enrichment logic lives in application code instead of in the schema where the data lives.

## With VillageSQL: HTTP from SQL

```sql theme={null}
INSTALL EXTENSION vsql_http;
```

`http_get(url)` makes a GET request and returns a JSON string with the full response.

```sql theme={null}
SET @resp = CONVERT(
  vsql_http.http_get('https://httpbin.org/get') USING utf8mb4
);

SELECT
  JSON_VALUE(@resp, '$.status')       AS status,
  JSON_VALUE(@resp, '$.content_type') AS content_type,
  JSON_UNQUOTE(JSON_EXTRACT(@resp, '$.content')) AS body;
```

Every function in `vsql_http` returns the same JSON shape:

| Field | Description |
| :- | :- |
| `status` | HTTP status code as a JSON number (`200`, `404`, etc.) |
| `content_type` | Value of the `Content-Type` response header |
| `headers` | Array of `[name, value]` pairs for all response headers |
| `content` | Response body |

### The charset conversion

On VillageSQL 0.0.6 and earlier, VEF STRING functions return binary charset. Before passing the result to `JSON_VALUE` or `JSON_EXTRACT`, wrap it with `CONVERT(... USING utf8mb4)`:

```sql theme={null}
-- On 0.0.6 and earlier, this throws an error — JSON_VALUE rejects binary charset
-- ERROR 3144: Cannot create a JSON value from a string with CHARACTER SET 'binary'
SELECT JSON_VALUE(vsql_http.http_get('https://httpbin.org/get'), '$.status');

-- This works on every version
SELECT JSON_VALUE(
  CONVERT(vsql_http.http_get('https://httpbin.org/get') USING utf8mb4),
  '$.status'
);
```

VillageSQL 0.0.7 and later return the correct charset directly, so `CONVERT` is no longer required there — but it is harmless to keep, and every example below still uses it for compatibility with 0.0.6 and earlier. Assign to a variable with `CONVERT` once and reuse the variable to avoid repeating the cast.

## POST Requests

`http_post(url, content_type, body)` sends a POST with a body:

```sql theme={null}
SET @resp = CONVERT(
  vsql_http.http_post(
    'https://httpbin.org/post',
    'application/json',
    '{"name": "widget", "qty": 3}'
  ) USING utf8mb4
);

SELECT
  JSON_VALUE(@resp, '$.status') AS status,
  JSON_UNQUOTE(JSON_EXTRACT(@resp, '$.content')) AS body;
```

## Custom Headers and Other Methods

`http_request(method, url, headers_json, body, content_type, options_json)` handles any method with custom headers:

```sql theme={null}
SET @resp = CONVERT(
  vsql_http.http_request(
    'GET',
    'https://api.example.com/secure',
    '{"Authorization": "Bearer mytoken", "Accept": "application/json"}',
    NULL,
    NULL,
    NULL
  ) USING utf8mb4
);
```

The `headers_json` argument is a JSON object where keys are header names and values are header values.

### All available functions

| Function | Description |
| :- | :- |
| `http_get(url)` | GET request |
| `http_post(url, content_type, body)` | POST with body |
| `http_put(url, content_type, body)` | PUT with body |
| `http_delete(url)` | DELETE request |
| `http_patch(url, content_type, body)` | PATCH with body |
| `http_request(method, url, headers_json, body, content_type, options_json)` | Any method, custom headers, options |
| `url_encode(text)` | Percent-encode a string |
| `url_decode(text)` | Decode a percent-encoded string |

All functions return NULL for NULL input. On connection failure (refused
connection, DNS failure, timeout), the HTTP functions also return NULL and
raise a warning carrying curl's error message — check it with `SHOW WARNINGS`
when a request seems to disappear.

## URL Encoding

`url_encode()` and `url_decode()` handle percent-encoding for query parameters:

```sql theme={null}
SELECT vsql_http.url_encode('hello world & more');
-- → hello%20world%20%26%20more

-- Build a URL with encoded parameters
SET @q = vsql_http.url_encode('SELECT * FROM users');
SET @url = CONCAT('https://api.example.com/search?q=', @q);
SET @resp = CONVERT(vsql_http.http_get(@url) USING utf8mb4);
```

## Known Limitations

**256KB response cap** — Responses larger than 256KB are truncated. This covers typical API payloads used in SQL queries; it's not suited for large file downloads. A response cut off by this cap is no longer valid JSON: `JSON_EXTRACT`/`JSON_UNQUOTE` raise `ERROR 3141` on it, while `JSON_VALUE` returns NULL with a `Warning 3141` instead of erroring, under its default `NULL ON ERROR` behavior.

**`JSON_VALUE` size limit** — `JSON_VALUE` returns NULL when the extracted value exceeds MySQL's internal size limit. For large response bodies, use `JSON_UNQUOTE(JSON_EXTRACT(...))` instead:

```sql theme={null}
-- Use JSON_UNQUOTE + JSON_EXTRACT for large content fields
SELECT JSON_UNQUOTE(JSON_EXTRACT(@resp, '$.content')) AS body;
```

**Synchronous** — Every HTTP call blocks until it completes or times out. `max_execution_time` does not bound or interrupt a blocking `vsql_http` call — a slow or unreachable endpoint holds the statement for its own connect/read timeout regardless. `http_get`/`http_post`/`http_put`/`http_delete`/`http_patch` take no options; only `http_request(method, url, headers_json, body, content_type, options_json)` accepts a timeout, as `{"timeout": N}` in seconds in its `options_json` argument (default 30). Use `http_request` instead of the shorthand functions when you need to shorten it (see [HTTP Webhooks](https://villagesql.com/docs/en/guides/http-webhooks.md)).

## Troubleshooting

| Problem | Solution |
| :- | :- |
| `FUNCTION <database>.http_get does not exist` | Run `INSTALL EXTENSION vsql_http` |
| Returns NULL | Connection failed or NULL input — check `SHOW WARNINGS` for curl's error message, verify the URL, and check network access from the server host |
| `JSON_VALUE` returns NULL | Response body too large — use `JSON_UNQUOTE(JSON_EXTRACT(...))` |
| Garbled JSON parsing | Forgot `CONVERT(... USING utf8mb4)` — wrap the function call |
| Query times out | Set a `{"timeout": N}` option on the request itself — `max_execution_time` has no effect on a blocking `vsql_http` call |

## Next Steps

* [Enriching rows with API data](https://villagesql.com/docs/en/guides/rest-api-enrichment.md) — pull live external data into query results
* [Sending webhooks from triggers](https://villagesql.com/docs/en/guides/http-webhooks.md) — push row changes to external services automatically

## See also

* [Enriching Rows with External API Data](https://villagesql.com/docs/en/guides/rest-api-enrichment.md) — a practical pattern for HTTP-based data enrichment
* [Sending Webhooks from Triggers](https://villagesql.com/docs/en/guides/http-webhooks.md) — event-driven HTTP calls from triggers
* [JSON in MySQL](https://villagesql.com/docs/en/guides/json-in-mysql.md) — parsing the JSON responses that HTTP requests return
* [Serving Tables as a REST API](https://villagesql.com/docs/en/guides/rest-api-from-tables.md) — the same server answering HTTP instead of making requests


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.