The one rule: a blank line before the body
Headers end where the first empty line appears; the body is everything after it, up to the next ### separator. This mirrors the actual HTTP wire format, which is why the file format works in every tool. Miss the blank line and your body's first line is parsed as a (broken) header — the single most common .http mistake:
# ✗ WRONG — no blank line: the body is read as headers
POST https://api.example.com/users
Content-Type: application/json
{ "name": "Ada" }
### ✓ RIGHT — one empty line separates headers from body
POST https://api.example.com/users
Content-Type: application/json
{ "name": "Ada" }
POST a JSON body
The everyday case: Content-Type: application/json, blank line, then the JSON — nested objects and arrays included, formatted however you like:
POST https://api.example.com/orders
Content-Type: application/json
Accept: application/json
{
"customerId": 42,
"items": [
{ "sku": "KV-100", "qty": 2 },
{ "sku": "KV-205", "qty": 1 }
],
"notes": null
}
The body is sent byte-for-byte as written — the client doesn't reformat, validate, or minify your JSON. If the server rejects it, what you see in the file is what the server saw.
Form data (x-www-form-urlencoded)
The encoding HTML forms and OAuth token endpoints use: key=value pairs joined by &. No form UI needed — it's just a body:
POST https://api.example.com/login
Content-Type: application/x-www-form-urlencoded
username=ada&password={{password}}&remember=true
The same shape drives OAuth token endpoints — the auth guide shows a full client_credentials exchange.
PUT and PATCH payloads
Update requests work identically — only the method changes. A full replace with PUT, a partial update with PATCH:
### Replace the whole resource
PUT https://api.example.com/users/42
Content-Type: application/json
{ "name": "Ada Lovelace", "email": "ada@example.com", "active": true }
### Change one field
PATCH https://api.example.com/users/42
Content-Type: application/json
{ "active": false }
Keeping the POST/PUT/PATCH trio for one resource in a single file, separated by ###, turns the file into living documentation of the endpoint — see the format guide for multi-request files.
Variables inside bodies
{{variable}} references work in the body exactly as they do in URLs and headers — declared with @name at the top of the file, or injected from a .env environment:
@tenantId = acme-prod
POST https://api.example.com/webhooks/test
Content-Type: application/json
{
"tenant": "{{tenantId}}",
"event": "order.created",
"secret": "{{webhookSecret}}"
}
Values like webhookSecret belong in a git-ignored .env file, not in the committed request — the environments guide shows the full setup.
What about file uploads?
Some ecosystem tools (the VS Code REST Client, the JetBrains HTTP Client) can reference an external file as the body with a line like < ./payload.json, and support multipart/form-data uploads. Karve v1 keeps bodies inline: text payloads written in the file itself, with no binary attachments. For JSON and form bodies — the bulk of REST debugging — inline is also the version your reviewers can actually read in a diff.
Honest scope note: if multipart binary uploads are a daily need, an ecosystem editor tool covers that today — your .http files stay compatible with both.
Common questions
How do I POST JSON in a .http file?
Request line, Content-Type: application/json header, one blank line, then the JSON. Everything up to the next ### is the body.
Why is my body being ignored?
Missing blank line after the headers, almost every time. Without it the body's first line is parsed as a header. Add one empty line and re-send.
How do I send form data?
Content-Type: application/x-www-form-urlencoded, blank line, then key=value&key2=value2 in the body.
Can I attach a file as the body?
Not in Karve v1 — bodies are inline text. Ecosystem editor tools support < ./file.json references if you need them; the same file still runs in Karve once the body is inlined.