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.

Example
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.

1. List every file on your site
curl https://whatimade.app/v1/keys/files \
  -H "X-API-Key: wim_your_key_here"
2. Read one file
curl https://whatimade.app/v1/keys/files/index.html \
  -H "X-API-Key: wim_your_key_here"
3. Write (create or replace) a file
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"}'
4. Delete a file
curl -X DELETE https://whatimade.app/v1/keys/files/old-page.html \
  -H "X-API-Key: wim_your_key_here"

Python Example

Read, modify, and write back
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.

HeaderRequiredDescription
X-API-KeyYesYour API key
Response
{
  "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.

HeaderRequiredDescription
X-API-KeyYesYour API key
Response
{
  "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.

URLDescription
/v1/keys/files/index.htmlRead the home page
/v1/keys/files/css/style.cssRead a nested file
Response (text)
{
  "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.

HeaderRequiredDescription
X-API-KeyYesYour API key
Content-TypeYesapplication/json
Request Body
{
  "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.

Response
{
  "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.

HeaderRequiredDescription
X-API-KeyYesYour API key
Response
{ "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.

Response
{
  "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"
  }
}
StatusCodeMeaning
400BAD_REQUESTSomething in the request was missing or malformed
401UNAUTHORIZEDMissing or invalid access key
403FORBIDDENThe key is valid but is not tied to the site you asked about
404NOT_FOUNDThe site or file you asked for does not exist
429RATE_LIMITEDToo 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 doingCap
Uploading files20 per minute
Any API request100 per minute
Publishing a new version5 per minute
Importing an existing website20 per minute

If you go over, we reply with a 429. Wait a minute and try again.