Nightjar Logo
Workflow Integration And Batching

How do I start Product Photography through the Nightjar API?

6 min read

To start Product Photography through the Nightjar API, authenticate a Team API Credential, choose an existing Asset or Product, and send POST /generations/product-photography with an Idempotency-Key. The response is an asynchronous Generation: poll its ID, then retrieve the Assets from completed output slots.

Nightjar's API uses the same Products, reusable photographic ingredients and Team Library as the web app. That makes it useful when your software needs to repeat an established product-photography setup: keep the Product evidence and ingredient IDs, change the subject or direction deliberately, and receive reusable Assets without rebuilding the brief for every image.

1. Check your Team and API access

Use the base URL https://api.nightjar.so/v1. A Team owner creates an API Credential in Settings → API. Creating images requires a credential with full permissions, an active paid Subscription and sufficient Team Credits. API Access is included with paid Subscriptions; it uses the Team's existing Credits.

Keep the credential in your server's secret storage and expose it to these shell examples as NIGHTJAR_API_KEY. Do not put it in browser code, source control or logs. Check the credential before creating resources:

curl --fail-with-body --silent --show-error \
  --header "Authorization: Bearer ${NIGHTJAR_API_KEY:?Set your API credential securely}" \
  'https://api.nightjar.so/v1/team'

Inspect credential.profile, credits, and capabilities.creative_writes_allowed. The response also supplies capabilities.api_concurrency.limit and used; use those returned values when adding concurrent requests. A read-only credential cannot start a Generation. See the API authentication quickstart.

2. Obtain an Asset or choose a Product

If the image already exists in this Team, use its public ID from GET /assets, or choose a reusable Product from GET /products. You can start Product Photography directly from an Asset; creating a Product is optional.

For a new source image, complete the upload before using it:

  1. Send POST /uploads with an idempotency key and JSON containing content_type and size_bytes. Use the actual MIME type and raw byte count of your file.
  2. Read the returned Session's id and upload object. Send the raw image bytes to upload.url using upload.method and every header in upload.headers, before upload.expires_at. Do not send your API Credential to this signed upload URL.
  3. Take upload_token from the successful direct-upload response. Send POST /uploads/{upload_id}/complete with its own idempotency key and {"upload_token":"<returned upload token>"}.
  4. Use the finalization response's Asset id. The Upload Session itself is not an Asset.

The complete upload quickstart provides runnable examples. Accepted source formats are JPEG, PNG, GIF, WebP and AVIF; Nightjar may normalize the stored format.

For repeated work, create a Product with POST /products, an idempotency key, and this illustrative body, replacing the placeholder with the returned Asset ID:

{
  "name": "Ceramic cup",
  "asset_ids": ["<your Asset ID>"],
  "primary_asset_id": "<your Asset ID>"
}

A Product groups its photos for reuse and can also hold a factual description and physical dimensions. When you send product_ids, Nightjar packs each Product's Main photo first, in Product order, then explicit asset_ids in your order, then remaining Product Photos round-robin. Duplicate Assets use one slot, and the resolved subject-photo limit is five. The combined number of explicit Product IDs and Asset IDs must also be at most five.

To prioritize an important detail view, include its Asset ID in asset_ids alongside product_ids. Check the Generation's resolved_asset_ids to see which subject photos were admitted. Main photos have priority, but Product IDs are not limited to Main photos alone.

Every selected subject appears together in every output. To photograph different catalog items separately, submit separate Generations.

3. Submit one controlled Single shot

Save the following illustrative JSON as product-photography.json, replacing the Asset placeholder with a real ID. This request asks for one square product-only image on white, with eye-level Framing and a soft contact Shadow:

{
  "asset_ids": ["<your Asset ID>"],
  "background": { "type": "color", "color": "#FFFFFF" },
  "fashion_model": { "type": "none" },
  "framing": "eye-level",
  "shadow": "soft",
  "output_mode": "single_shots",
  "image_count": 1,
  "aspect_ratio": "1:1",
  "resolution": "1k",
  "output_format": "jpeg"
}

Set NIGHTJAR_REQUEST_KEY to a unique value for this logical request, such as a UUID generated by your application. Persist that key with the request body before submitting. Running this command creates real work and reserves one Credit; a completed output costs one Credit.

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Authorization: Bearer ${NIGHTJAR_API_KEY:?Set your API credential securely}" \
  --header "Idempotency-Key: ${NIGHTJAR_REQUEST_KEY:?Set and save a unique request key}" \
  --header 'Content-Type: application/json' \
  --data-binary @product-photography.json \
  'https://api.nightjar.so/v1/generations/product-photography'

To use a Product instead, replace asset_ids with product_ids containing its public ID. Add photography_style_id to reuse a Photography Style, or select a saved scene with background: {"type":"background","background_id":"<your Background ID>"}. Remove shadow when switching away from a flat-color background. Use custom_directions for written refinements. The workflow reference covers on-model controls and other supported combinations.

The API has no Recipe endpoint or Recipe-ID input. Save ingredient IDs and explicit request settings in your integration to reuse photographic direction.

4. Choose Single shots or Photoshoot deliberately

single_shots supports one to six independent outputs. Set image_count explicitly: omitting it requests two. Each completed 1K or 2K output costs one Credit; each completed 4K output costs two.

photoshoot requests a cohesive four-image set for two Credits total, at 1K or 2K. It varies the camera decisions across the set, so omit image_count, framing, shadow, pose_id and camera_distance. For example:

{
  "product_ids": ["<your Product ID>"],
  "background": { "type": "automatic" },
  "fashion_model": { "type": "none" },
  "custom_directions": "A quiet breakfast-table setting with warm daylight.",
  "output_mode": "photoshoot",
  "aspect_ratio": "4:5",
  "resolution": "2k",
  "output_format": "webp"
}

Use a new idempotency key for this different request. The complete planned Credit amount is reserved before admission. Photoshoot charges the full two Credits if any output succeeds, even if other slots fail; if all fail, the charge is zero. Single shots settle per completed output.

5. Poll the Generation and retrieve successful Assets

A successful submission returns HTTP 202 with a Generation id. Save it as NIGHTJAR_GENERATION_ID and read its current state:

curl --fail-with-body --silent --show-error \
  --header "Authorization: Bearer ${NIGHTJAR_API_KEY:?Set your API credential securely}" \
  "https://api.nightjar.so/v1/generations/${NIGHTJAR_GENERATION_ID:?Set the returned Generation ID}"

Repeat that GET with a delay while status is queued or processing. Stop when it is completed or failed. A local polling timeout does not cancel the Generation; keep its ID and resume reading it later. Accepted Generations cannot be canceled.

completed means at least one output succeeded, not that every requested image exists. Inspect every entry in outputs:

  • For status: "completed", take asset.id and request GET /assets/{asset_id}. The Asset response contains its image url, dimensions and format.
  • For status: "failed", inspect error.code, error.message and error.retryable. Keep successful Assets when another slot fails.
  • Read credits.charged for final settlement. It is null while the Generation is nonterminal.

Asset URLs are public by link while the underlying files exist, so share them deliberately. Review the returned images against the real product before publishing them.

How should retries and API errors work?

If submission times out or the connection drops, retry the same body with the same idempotency key. Within the retained idempotency record, that replays the original admission instead of creating another Generation. Keys are scoped to the credential, HTTP method and concrete path, and are retained for at least 24 hours. An in-flight duplicate returns 409; reusing a key for changed intent returns 422.

Replaying admission does not refresh the Generation or rerun failed outputs. Poll the Generation for current state. A retryable terminal output failure allows a new logical request with a new key, which creates new work and may spend Credits. For Single shots, request only the number of replacement outputs you need; resending the original count also creates replacements for outputs that already succeeded.

For other errors, read the problem response's code and keep the Request-Id response header for support. Honor Retry-After on 429 responses and use backoff. Check /team again for permission, Subscription, Credit or concurrency problems before resubmitting. The API error reference maps stable codes to recovery actions.

Consistent and on brand AI photoshoots, optimized for conversion.

Nightjar