PierNode
Uploads

Uploading an image

Upload your own image so it can be used as input.

Some endpoints take an image you supply rather than only a prompt. You upload it once, get back an id, and pass that id wherever an image input is accepted.

PierNode does not fetch images from URLs you provide. Uploading is the only way to bring your own image in, which keeps every input under your account and inside storage PierNode controls.

Uploads are not billed.

The flow

  1. POST /api/v1/uploads — reserve an upload and get a URL to send bytes to.
  2. PUT the file to that URL.
  3. POST /api/v1/uploads/{assetId}/complete — PierNode checks the file and marks it ready.

Only after step 3 is the upload usable.

1. Create the upload

curl -X POST https://app.piernode.com/api/v1/uploads \
  -H "Authorization: Bearer $PIERNODE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contentType": "image/png"}'

Request

FieldTypeRequiredNotes
contentTypestringyesimage/jpeg, image/png or image/webp.

That is the whole request. There is no way to choose where the file is stored, and any other field you send is ignored.

Response

{
	"id": "c8f2a1d4e5b64c9a8f3d2e1b0a9c8d7e",
	"uploadUrl": "https://...",
	"method": "PUT",
	"expiresAt": "2026-01-01T12:15:00.000Z"
}

Keep the id. The uploadUrl is for the next step only and is not needed again.

2. Send the bytes

PUT the file to uploadUrl with the same Content-Type you declared. The URL is signed for that content type, so a different one is rejected.

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/png" \
  --data-binary @photo.png

A successful PUT means the bytes arrived. It does not mean the file was accepted — that is decided in the next step.

3. Complete the upload

curl -X POST https://app.piernode.com/api/v1/uploads/\{assetId\}/complete \
  -H "Authorization: Bearer $PIERNODE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

PierNode reads the file's header and confirms it really is the image type you declared, then records its dimensions.

{
	"id": "c8f2a1d4e5b64c9a8f3d2e1b0a9c8d7e",
	"status": "READY",
	"contentType": "image/png",
	"sizeBytes": 284913,
	"width": 1024,
	"height": 768,
	"createdAt": "2026-01-01T12:00:00.000Z",
	"expiresAt": "2026-01-01T12:15:00.000Z",
	"verifiedAt": "2026-01-01T12:00:42.000Z",
	"error": null
}

This endpoint is safe to call more than once: once an upload has settled, the same answer comes back without the file being read again.

Status values

StatusMeaning
PENDINGReserved; the file has not been verified yet.
READYVerified and usable as input.
INVALIDThe file was rejected. See error.code.
EXPIREDThe upload window closed before the upload was completed.

An upload that reaches INVALID or EXPIRED stays that way. Uploading a different file afterwards does not change the verdict — create a new upload.

Rejection reasons

error.codeMeaning
UNSUPPORTED_FORMATThe bytes are not a JPEG, PNG or WebP.
CONTENT_TYPE_MISMATCHThe file is an image, but not the type you declared.
FILE_TOO_LARGELarger than 20 MB.
FILE_TOO_SMALLToo small to be a real image.
DIMENSIONS_OUT_OF_RANGEOutside the accepted size range.
UPLOAD_EXPIREDThe upload window closed first.

If you call complete before the bytes have arrived, you get a 400 and the upload stays PENDING, so you can retry.

Limits

LimitValue
FormatsJPEG, PNG, WebP
Maximum file size20 MB
Dimensions16–8192 px per side, up to 40 megapixels
Upload window15 minutes

The declared content type is only a hint. What the file actually is gets decided by reading it, so renaming a .zip to .png will not get it through.

4. Check an upload

curl https://app.piernode.com/api/v1/uploads/\{assetId\} \
  -H "Authorization: Bearer $PIERNODE_API_KEY"

Returns the same shape as complete. Uploads belonging to another account return 404, exactly as an id that does not exist does.

How long URLs last

Three different links are involved, and they expire on different clocks:

LinkLifetimeWhat it is for
Upload URL15 minutesThe one-off PUT in step 2. Covers one file and nothing else.
Generation input30 minutesMinted internally when a generation reads your image. Never returned to you.
Generation output60 minutesResult links from GET /api/v1/tasks/\{taskId\}. Re-poll the task for a fresh one.

Refer to an upload by its id. That id does not expire while the upload exists, so it is the thing worth storing.

On this page