AI Image API Audit Logging: Trace Jobs Without Leaking Data
A customer reports two charges for one image, but your logs contain an entire request body and no stable operation identifier. AI image API audit logging should explain what happened without turning your logging platform into another repository of customer prompts, reference images, and credentials.
For a GenRelay integration, start with application-owned events around submission, response handling, storage, and billing reconciliation. This problem-solving guide provides a small executable event validator and a practical investigation workflow. The event names and fields below belong to your application; they are not claims about a GenRelay logging endpoint or response schema.
What should an image generation audit log record?
Record the actor, operation, state transition, and evidence needed to reconstruct a generation attempt, rather than the complete request or response.
An audit log is a chronological record used to establish who performed an action and what the system observed. Operational telemetry, such as latency histograms, serves a related but different purpose: detecting service behavior across many requests. Keep detailed audit records access-controlled, and publish aggregated metrics separately.
Use these application-owned fields as a starting point:
| Field | Meaning | Handling rule |
|---|---|---|
| event_id | Unique identifier for one event | Generated by the logging service or backend |
| operation_id | One customer-requested generation action | Stable across related attempts |
| attempt_id | One submission attempt | New value for each actual submission |
| tenant_id | Internal account reference | Derived from authenticated server context |
| event | Validated lifecycle event name | Selected from an allowlist |
| occurred_at | UTC observation time | Assigned server-side |
| model_profile | Internal model configuration reference | Do not include a raw request object |
| outcome | Normalized observation | Avoid unrestricted exception text |
A provider job identifier, when the selected API returns one, can live in a restricted mapping table linked to attempt_id. Avoid making identifiers public merely because they are not passwords. They may still expose usage patterns or enable unauthorized lookups in a poorly designed application.
Which lifecycle events make an investigation possible?
Log durable observations at important boundaries, and distinguish observed success from assumptions about remote execution.
A useful sequence is request_accepted, submission_started, submission_acknowledged, output_stored, and charge_reconciled. Also support submission_unknown and request_rejected. These names are illustrative local conventions. Map actual API responses into them after validating the documented response format.
A submission timeout does not establish that generation failed. If the remote service accepted work before the connection ended, a second submission could create another billable attempt. Record the ambiguous state and reconcile it through available documented mechanisms before deciding whether to submit again.
Likewise, output_stored means your application persisted an artifact; it does not imply that a customer approved the image. Record quality review and customer delivery separately when those steps matter. A billing event should reference confirmed usage evidence instead of inferring a charge solely from an HTTP status code.
Persist state changes and an outgoing event record in the same database transaction where possible. This is the transactional outbox pattern: application state and an event waiting for delivery are committed together. The event exporter can retry delivery using event_id for deduplication. This reduces gaps between your job database and external log storage.
How can Python enforce a small logging contract?
Use a strict allowlist, validate identifiers, and construct the log envelope on the server rather than accepting arbitrary dictionaries from clients.
The following example runs with Python's standard library. Save it as audit_events.py. Its UUID requirement is a local design choice, not an API requirement. Production deployments should additionally enforce authentication, authorization, storage limits, and access controls around the writer.
import json
from datetime import datetime, timezone
from uuid import UUID, uuid4
EVENTS = {
"request_accepted", "submission_started",
"submission_acknowledged", "submission_unknown",
"output_stored", "charge_reconciled", "request_rejected",
}
REQUIRED = {"operation_id", "attempt_id", "tenant_id", "event"}
def make_event(fields):
if set(fields) != REQUIRED:
raise ValueError("unexpected or missing audit fields")
if fields["event"] not in EVENTS:
raise ValueError("unknown event")
validated = {"event": fields["event"]}
for key in ("operation_id", "attempt_id", "tenant_id"):
validated[key] = str(UUID(fields[key]))
return {
**validated,
"event_id": str(uuid4()),
"occurred_at": datetime.now(timezone.utc).isoformat(),
}
if __name__ == "__main__":
fields = {
"operation_id": str(uuid4()),
"attempt_id": str(uuid4()),
"tenant_id": str(uuid4()),
"event": "submission_started",
}
event = make_event(fields)
assert "prompt" not in event
try:
make_event({**fields, "authorization": "test-secret"})
except ValueError:
pass
else:
raise AssertionError("unknown fields must be rejected")
print(json.dumps(event, sort_keys=True))
Run python3 audit_events.py locally. This test makes no generation request and incurs no generation charge. It demonstrates rejection of unexpected fields; it does not establish that surrounding middleware, reverse proxies, or error trackers are also safe.
The validator deliberately rejects extra data instead of silently storing it. A general-purpose sanitization function that searches for familiar secret names can miss new fields, nested payloads, and signed download URLs. Explicit schemas make additions reviewable during code changes.
How do you keep prompts and credentials out of logs?
Prevent sensitive data from entering logging calls in the first place, then inspect every system that might capture requests automatically.
Disable raw request and response capture for generation routes. Review HTTP debug logging, exception reporting, distributed tracing, and customer-support exports. A signed URL is a bearer capability: someone holding a valid URL may gain access to its resource. Do not record its query string in ordinary logs.
For prompt investigations, store a restricted reference to a versioned prompt record rather than the prompt itself. Apply tenant-specific authorization to that lookup. The prompt versioning guide explains how to separate reproducibility metadata from image identity.
If you use a digest for correlation, understand its limits. A plain hash of predictable text can be guessed by hashing candidate prompts. A keyed message authentication code can reduce that particular exposure, but it remains sensitive correlation metadata and needs key management. Neither technique makes customer content automatically anonymous.
Add automated checks using synthetic credentials and test prompts. Generate an intentional error, inspect every configured sink, and confirm that forbidden strings did not appear. Repeat after upgrading SDKs or enabling observability integrations.
How should cost evidence be attached to an audit trail?
Store estimated and settled charges as separate records tied to the generation attempt, with the price version and billing unit recorded explicitly.
As of November 4, 2026, the supplied GenRelay image prices are:
| Model | Output tier | Listed generation price |
|---|---|---|
| Nano Banana Pro | 1K | $0.030 per image |
| Nano Banana Pro | 2K | $0.030 per image |
| Nano Banana Pro | 4K | $0.042 per image |
| Nano Banana 2 | 1K | $0.020 per image |
| Nano Banana 2 | 4K | $0.036 per image |
| GPT-Image-2 | Listed image output | $0.014 per image |
For an illustrative batch of 200 billed Nano Banana Pro 2K outputs, generation spend is 200 × $0.030 = $6.00. If the application records 200 accepted operations but 210 billed outputs, investigate attempt counts and reconciliation evidence; do not assume that the difference proves a provider error.
These figures exclude your storage, transfer, review, and application infrastructure expenses. Do not treat an estimated charge as confirmation of actual billing, and do not infer unlisted output tiers from the table. Review the image API overview and Nano Banana model page when choosing a model configuration.
What access and retention controls belong around audit data?
Restrict audit queries by role and tenant, and retain records for a documented business purpose rather than indefinitely.
Support staff generally need a narrow investigation view, not unrestricted access to every account. Record privileged access to logs, separate export permissions from search permissions, and protect archived copies as carefully as the primary index. Internal identifiers can still constitute personal or commercially sensitive information when linked with account data.
Choose retention periods with your legal and security teams based on contractual obligations and operational needs. Deletion workflows must account for indexes, archives, and exports. Do not promise that deleting an image immediately deletes every billing or security record; explain those categories separately in your product documentation.
What do developers ask about image API audit logging?
The common questions concern troubleshooting detail, duplicate events, credentials, and ambiguous submissions.
Should I log the full prompt to debug image quality?
Not in general-purpose logs. Keep authorized, purpose-limited prompt records separately if your product requires them, and reference those records from the audit trail.
Can I put an API key fingerprint in every event?
Prefer an internal credential record identifier that cannot authenticate a request. Never store the credential itself, and review whether even its identifier is necessary for the investigation.
Does a repeated event mean the image was generated twice?
No. An exporter can deliver the same event more than once. Deduplicate by event_id and examine distinct attempt identifiers and confirmed usage before concluding that two generations occurred.
Should a timeout be logged as a failed generation?
Log it as an unknown submission outcome unless you have authoritative evidence of failure. Preserve the operation and attempt mapping so the next worker can reconcile instead of blindly resubmitting.