Validate an uploaded asset and publish its CDN URL
Step 2 of 2, called after the bytes have been PUT to the presigned URL. Validates the real content type from the file's magic bytes, moves the object under its permanent key, runs image moderation synchronously, and returns the CDN URL. Idempotent — confirming an already-stored asset returns the same cdn_url. moderation_status is always populated; pending is the fail-closed value used when the provider could not be reached.
Parameters
Replays with the same key return the original result. **Key shape.** A non-empty string of at most 255 characters after trimming surrounding whitespace. An empty or oversized key is rejected with 422 `VALIDATION_ERROR` (`idempotency_key_empty` / `idempotency_key_too_long`) rather than silently running the request unprotected. Leading/trailing whitespace is trimmed, so `" k "` and `"k"` are the same key. **Detecting a replay.** A response served from the idempotency cache rather than by re-executing the operation carries the response header `Idempotency-Replayed: true`; a freshly executed response omits the header entirely. **Replay window.** A record is retained for 24 h by default, after which the same key is free again and the operation re-executes. Only a 2xx outcome is recorded — a request that failed can be retried with the same key. **Conflicts.** Reusing a key with a different request is `409 idempotency_key_conflict`. The request identity is the method, path, query string and body, except on `POST /transactional/send`, where only `domain_id` + `to` (case-insensitive) + `template_id` + `variables` + `send_at` are compared (the parsed instant normalised to ISO-8601, and omitted entirely rather than sent as `null` when the request carried no `send_at`); a replay of that operation does **not** carry `Idempotency-Replayed`. **Concurrency.** A key is claimed before the operation executes, so two requests carrying the same key can never both run. The second one is rejected with `409 idempotency_in_progress` while the first is still in flight. This is a *transient* conflict — distinct from `idempotency_key_conflict` above, which is permanent — so retry after a short delay: once the first request completes, the same key replays its result. A claim is released as soon as the first request fails or returns a non-2xx, and expires after 5 min if the process handling it dies, so a key can never be locked indefinitely. **Rate limiting.** A replay short-circuits before the rate limiter, so it consumes no quota and returns no `X-RateLimit-*` headers. This is deliberate: a client retrying after a timeout must not be penalised for work that was already done. **Scope.** Keys are scoped per tenant.
Responses
Example response · 201
{
"status": "success",
"http_code": 0,
"payload": {
"cdn_url": "string",
"moderation_status": "pending"
},
"meta": []
}
Example response · 404
{
"status": "error",
"http_code": 0,
"payload": [],
"meta": {
"reason": "VALIDATION_ERROR",
"validation_error": [
{
"field": "string",
"message": "string"
}
],
"details": []
}
}
Example response · 422
{
"status": "error",
"http_code": 0,
"payload": [],
"meta": {
"reason": "VALIDATION_ERROR",
"validation_error": [
{
"field": "string",
"message": "string"
}
],
"details": []
}
}