For developers and AI tools
This page is the technical reference for people who want to wire an outside tool, or an AI assistant like ChatGPT or Claude, into their WhatIMade site. If that isn't you, head back to your dashboard - you can do everything this page describes from there, with buttons and menus, no code required.
Not sure if you need this page? You don't. The dashboard lets you change any file on any of your sites, upload new ones, and publish new versions, all from your browser. This page exists only for people who want to let an outside program or AI do the same thing on their behalf.
Overview
WhatIMade lets you create a small access key tied to one of your sites. Hand that key to an AI assistant or a script, and it can read the files on that site, change them, and put a new version live - just like you do in the dashboard, but without you clicking.
Base address to send requests to: https://whatimade.app/v1
All responses come back as JSON. That is a standard text format that AI assistants and programming languages already know how to read.
How you identify yourself
You create an access key from your site's AI Access tab in the dashboard. Every request you make has to include that key in a header called X-API-Key.
Treat the key like a password. Anyone who has it can read and change files on the site it belongs to. You can delete any key at any time from the dashboard; the moment you do, it stops working.
curl https://whatimade.app/v1/keys/verify \
-H "X-API-Key: wim_your_key_here"
Quick start
Each key is tied to one site. We remember which site the key belongs to, so you never need to tell us which site each time; we already know.
curl https://whatimade.app/v1/keys/files \
-H "X-API-Key: wim_your_key_here"
curl https://whatimade.app/v1/keys/files/index.html \
-H "X-API-Key: wim_your_key_here"
curl -X PUT https://whatimade.app/v1/keys/files/index.html \
-H "X-API-Key: wim_your_key_here" \
-H "Content-Type: application/json" \
-d '{"content": "<h1>Hello</h1>", "encoding": "utf-8"}'
curl -X DELETE https://whatimade.app/v1/keys/files/old-page.html \
-H "X-API-Key: wim_your_key_here"
Python Example
import requests
API_KEY = "wim_your_key_here"
BASE = "https://whatimade.app/v1"
HEADERS = {"X-API-Key": API_KEY}
# Read
r = requests.get(f"{BASE}/keys/files/index.html", headers=HEADERS)
payload = r.json()
html = payload["content"]
# Modify
html = html.replace("Hello", "Hello World")
# Write
requests.put(f"{BASE}/keys/files/index.html",
headers={**HEADERS, "Content-Type": "application/json"},
json={"content": html, "encoding": "utf-8"})
Give your AI these instructions
Copy the block below and paste it into a chat with ChatGPT, Claude, Gemini, or any AI assistant. Replace the placeholder key with your own one from the dashboard. From then on, you can just ask the assistant in plain English - "change the headline on the home page to..." - and it will talk to us on your behalf.
I have a website hosted on WhatIMade.app.
You can read and edit the files on it using this site-scoped API key.
The key already picks the correct site, so you never pass a site ID.
API Key: wim_your_key_here
Base URL: https://whatimade.app/v1
Endpoints:
- GET /keys/files - list every file on the site
- GET /keys/files/<path> - read one file as JSON
- PUT /keys/files/<path> - write a file (JSON body: {content, encoding})
- DELETE /keys/files/<path> - delete a file
- GET /keys/sites - get site info (URL, title, settings)
Always send: X-API-Key: wim_your_key_here
Check that a key works
GET /v1/keys/verify
A quick test. Confirms the key is still active, and tells you which site it is tied to.
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key |
{
"valid": true,
"site": {
"id": "abc123",
"subdomain": "my-portfolio",
"access_type": "public"
},
"key": {
"id": "key123",
"name": "ChatGPT access",
"last_used_at": 1713206400000
}
}
List the files on the site
GET /v1/keys/files
Returns every file that currently makes up the site this key is tied to.
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key |
{
"site": { "id": "abc123", "subdomain": "my-portfolio" },
"files": [
{ "path": "index.html", "size": 2048, "contentType": "text/html", "uploaded": "2026-04-17T10:00:00.000Z" },
{ "path": "css/style.css", "size": 512, "contentType": "text/css", "uploaded": "2026-04-17T10:00:00.000Z" }
]
}
Read one file
GET /v1/keys/files/<path>
Opens a single file and returns what is inside it. Text files (HTML, CSS, JavaScript) come back as plain text. Picture or font files up to 2 MB come back in a scrambled-but-readable-by-computer form. Anything bigger than 2 MB is rejected by this route - for those, just link to the public URL of the file on the site instead.
| URL | Description |
|---|---|
/v1/keys/files/index.html | Read the home page |
/v1/keys/files/css/style.css | Read a nested file |
{
"path": "index.html",
"contentType": "text/html",
"size": 2048,
"encoding": "utf-8",
"content": "<!doctype html><html>..."
}
Add or change a file
PUT /v1/keys/files/<path>
Puts a file at the given path. If the file already exists, it is replaced. The body is a simple JSON object - no file-upload form needed.
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key |
Content-Type | Yes | application/json |
{
"content": "<h1>Hello</h1>",
"encoding": "utf-8"
}
For pictures or fonts, set encoding to "base64" and encode the file that way (every AI assistant and programming language has a one-line helper for this). Limit is 25 MB per file.
{
"success": true,
"path": "index.html",
"size": 2048,
"contentType": "text/html"
}
Delete a file
DELETE /v1/keys/files/<path>
Removes a file from the site. The change takes effect at once - the file stops appearing the instant this call finishes.
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key |
{ "success": true, "deleted": "old-page.html" }
Site details
GET /v1/keys/sites
Returns the basic facts about the site this key belongs to: its address, its title, whether it is public or password-protected.
{
"site": {
"id": "abc123",
"subdomain": "my-portfolio",
"custom_domain": null,
"created_at": 1713206400000,
"updated_at": 1713206400000,
"access_type": "public",
"title": "My Portfolio",
"description": null
}
}
When something goes wrong
If a request cannot be completed, we reply with a short explanation in an error field. The response will look like this:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid API key"
}
}
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | Something in the request was missing or malformed |
| 401 | UNAUTHORIZED | Missing or invalid access key |
| 403 | FORBIDDEN | The key is valid but is not tied to the site you asked about |
| 404 | NOT_FOUND | The site or file you asked for does not exist |
| 429 | RATE_LIMITED | Too many requests too fast; see Rate Limits below |
Rate limits
To keep the service quick for everybody, we cap how many requests can come from the same place in a minute:
| What you are doing | Cap |
|---|---|
| Uploading files | 20 per minute |
| Any API request | 100 per minute |
| Publishing a new version | 5 per minute |
| Importing an existing website | 20 per minute |
If you go over, we reply with a 429. Wait a minute and try again.