Upload images, including AI generated ones
Upload an image to Ur Next Door as base64 JSON or a multipart file, then use data.url as a featured image or in a gallery. Limits and AI image tips.
Images go up in their own request, then you point a post (or question, poll, MCQ, service or job) at them. Two steps:
POST /api/v1/mediawith the image. The response containsdata.url, a path like/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp.- Send that path as
featured_image, or ingallery, when you create the content.
Endpoint: POST /api/v1/media · Scope: media · Quota: media
Limits
| Rule | Value |
|---|---|
| Formats in | PNG, JPEG, GIF or WebP |
| Size in | Up to 8 MB per image |
| Stored as | WebP, which must end at 2 MB or less after conversion |
| JSON request body | Up to 12 MB, so an 8 MB image still fits after base64 encoding |
gallery | Up to 12 images per item; jobs take featured_image only |
| One request | One image |
If the converted image is still over 2 MB, the upload fails. Resize it (for example to 1600 pixels on the long side) or compress it, and try again. Very large photos straight from a camera or an image model usually need this.
Option A: base64 JSON (best for AI tools)
Send the image as base64 in image_base64. It can be raw base64 or a data URI such as data:image/png;base64,iVBOR.... Add a filename (used to name the stored file) and a context that says what the image is for: post (the default), question, poll, mcq, community, service, job or misc.
# Build the body in a file: a base64 image is too long for a command line argument.
printf '{"image_base64": "%s", "filename": "cover.png", "context": "post"}' \
"$(base64 < cover.png | tr -d '\n')" > upload.json
curl -X POST "https://urnextdoor.com/api/v1/media" \
-H "Authorization: Bearer $UND_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data-binary @upload.jsonimport { readFile } from "node:fs/promises";
const image = await readFile("cover.png");
const res = await fetch("https://urnextdoor.com/api/v1/media", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.UND_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
image_base64: image.toString("base64"),
filename: "cover.png",
context: "post",
}),
});
console.log(res.status, await res.json());import base64
import os
import uuid
import requests
api_key = os.environ["UND_API_KEY"]
with open("cover.png", "rb") as f:
image_base64 = base64.b64encode(f.read()).decode("ascii")
response = requests.post(
"https://urnextdoor.com/api/v1/media",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"image_base64": image_base64,
"filename": "cover.png",
"context": "post",
},
timeout=30,
)
print(response.status_code, response.json())<?php
$apiKey = getenv('UND_API_KEY');
$payload = [
'image_base64' => base64_encode(file_get_contents('cover.png')),
'filename' => 'cover.png',
'context' => 'post',
];
$ch = curl_init('https://urnextdoor.com/api/v1/media');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status, PHP_EOL, $body, PHP_EOL;Sample response: 201 Created
{
"ok": true,
"data": {
"id": 318,
"context": "post",
"community_id": null,
"url": "/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
"disk_path": "aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
"original_name": "cover.png",
"extension": "webp",
"mime_type": "image/webp",
"size_bytes": 184320,
"created_at": "2026-10-15 09:00:00",
"updated_at": "2026-10-15 09:00:00"
},
"web_url": "https://urnextdoor.com/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
"request_id": "9f8e7d6c5b4a3921"
}Build big bodies in a file
A base64 image is too long to pass as a command line argument (Linux limits one argument to about 128 KB). The cURL example writes the body to upload.json and sends the file with --data-binary @upload.json. Agents should do the same.
Option B: multipart file upload
The same upload the website uses: a multipart/form-data request with the image in a field named file, plus optional context and community fields. Do not set Content-Type yourself; your HTTP client adds it with the boundary.
curl -X POST "https://urnextdoor.com/api/v1/media" \
-H "Authorization: Bearer $UND_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@cover.png" \
-F 'context=post'import { openAsBlob } from "node:fs";
const form = new FormData();
form.append("file", await openAsBlob("cover.png", { type: "image/png" }), "cover.png");
form.append("context", "post");
const res = await fetch("https://urnextdoor.com/api/v1/media", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.UND_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
},
body: form,
});
console.log(res.status, await res.json());import os
import uuid
import requests
api_key = os.environ["UND_API_KEY"]
response = requests.post(
"https://urnextdoor.com/api/v1/media",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": str(uuid.uuid4()),
},
files={"file": ("cover.png", open("cover.png", "rb"), "image/png")},
data={
"context": "post",
},
timeout=30,
)
print(response.status_code, response.json())<?php
$apiKey = getenv('UND_API_KEY');
$ch = curl_init('https://urnextdoor.com/api/v1/media');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => [
'file' => new CURLFile('cover.png', 'image/png'),
'context' => 'post',
],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status, PHP_EOL, $body, PHP_EOL;Sample response: 201 Created
{
"ok": true,
"data": {
"id": 318,
"context": "post",
"community_id": null,
"url": "/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
"disk_path": "aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
"original_name": "cover.png",
"extension": "webp",
"mime_type": "image/webp",
"size_bytes": 184320,
"created_at": "2026-10-15 09:00:00",
"updated_at": "2026-10-15 09:00:00"
},
"web_url": "https://urnextdoor.com/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
"request_id": "9f8e7d6c5b4a3921"
}Use the image
Pass data.url from the upload as featured_image, or put several in gallery:
curl -X POST "https://urnextdoor.com/api/v1/posts" \
-H "Authorization: Bearer $UND_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"title": "Five easy ways to save water at home",
"body": "<p>Small habits add up. These five saved our building a lot of water this summer.</p><ul><li>Fix dripping taps the same week.</li><li>Run the washing machine only when it is full.</li></ul>",
"featured_image": "/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
"tags": [
"water",
"home"
]
}'const res = await fetch("https://urnextdoor.com/api/v1/posts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.UND_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
title: "Five easy ways to save water at home",
body: "<p>Small habits add up. These five saved our building a lot of water this summer.</p><ul><li>Fix dripping taps the same week.</li><li>Run the washing machine only when it is full.</li></ul>",
featured_image: "/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
tags: ["water", "home"],
}),
});
console.log(res.status, await res.json());import os
import uuid
import requests
api_key = os.environ["UND_API_KEY"]
response = requests.post(
"https://urnextdoor.com/api/v1/posts",
headers={
"Authorization": f"Bearer {api_key}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"title": "Five easy ways to save water at home",
"body": "<p>Small habits add up. These five saved our building a lot of water this summer.</p><ul><li>Fix dripping taps the same week.</li><li>Run the washing machine only when it is full.</li></ul>",
"featured_image": "/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp",
"tags": ["water", "home"],
},
timeout=30,
)
print(response.status_code, response.json())<?php
$apiKey = getenv('UND_API_KEY');
$payload = [
'title' => 'Five easy ways to save water at home',
'body' => '<p>Small habits add up. These five saved our building a lot of water this summer.</p><ul><li>Fix dripping taps the same week.</li><li>Run the washing machine only when it is full.</li></ul>',
'featured_image' => '/storage/aarav/post/cover_3f9a1c2b7d4e_1792054800.webp',
'tags' => ['water', 'home'],
];
$ch = curl_init('https://urnextdoor.com/api/v1/posts');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status, PHP_EOL, $body, PHP_EOL;Sample response: 201 Created
{
"ok": true,
"id": 41,
"slug": "five-easy-ways-to-save-water-at-home",
"url": "/posts/five-easy-ways-to-save-water-at-home",
"web_url": "https://urnextdoor.com/posts/five-easy-ways-to-save-water-at-home",
"item": {
"id": 41,
"type": "post",
"slug": "five-easy-ways-to-save-water-at-home"
},
"request_id": "9f8e7d6c5b4a3921"
}You can also skip the upload and pass any https:// image URL. Ur Next Door links to it rather than copying it, so the image disappears if the other site removes it. The API does not fetch images from URLs for you.
Images made by AI tools
A typical agent flow, for example in Claude Code or Codex CLI:
- Generate or receive the image file (for example
cover.pngfrom your image model). - Check its size. If it is over 8 MB, or likely to be over 2 MB as WebP, resize it first.
- Upload it with base64 JSON, building the body in a file.
- Read
data.urlfrom the response. - Create the post with
featured_imageset to that path. - Give the member the post's
web_url.
ChatGPT custom GPTs are different: an Action can only send text, up to about 100,000 characters per request, so base64 only works for very small images (about 70 KB). In a GPT, pass an https:// image URL instead. See ChatGPT Actions.
Common errors
| Status and code | Cause | Fix |
|---|---|---|
403 insufficient_scope | The key lacks media | Create a key with media |
413 payload_too_large | The image is over 8 MB, or the body is over the limit | Resize or compress |
415 unsupported_media_type | Not PNG, JPEG, GIF or WebP, or a wrong Content-Type | Convert the image; send JSON or multipart |
422 validation_failed | No image_base64 and no file, or base64 that does not decode to an image | Check the field name and the encoding |
429 quota_exceeded | Today's media quota is used up | Wait until 00:00 UTC |
Frequently asked questions
Can I use an image URL instead of uploading?
Yes. featured_image and gallery accept any https image URL. It is linked, not copied, so it breaks if the source removes it. Upload the image if you want Ur Next Door to keep its own copy.
Can the API fetch an image from a URL and store it?
Not today. Importing an image from a URL is on the roadmap. Download it yourself and upload it with POST /api/v1/media.
Does an upload count against my quota even if I never use the image?
Yes. Every successful upload uses one unit of the daily media quota. Failed uploads do not count.
Next steps
- Create a post with your image.
- Prompt recipes for agents that generate images.
- Rate limits and daily quotas.
Last updated