Skip to main content
Before it sends the first network request, the CLI writes supported mutations to a private local journal. The journal is a recovery queue for requests whose result you don’t know — not a history of everything you have done. Successes and definitive rejections leave no record.

How it works

Each mutation command is one logical request:
  • Omit --idempotency-key and the CLI generates one.
  • Supply a stable key to identify the same logical request across invocations.
  • Re-running with the same key and the same input continues the unresolved operation and returns ordinary success output, not a replay envelope.
  • Re-running with the same key and different input fails before any request with IDEMPOTENCY_KEY_REUSED.
  • Use operation replay <id> when the original arguments or private input are inconvenient or gone. Replay reads the saved input from the journal.
Photon is the final authority on idempotency. Even if the local record is gone, sending the same key and input can still return the service’s cached result.

Inspect the queue

list returns unresolved records only, newest first, and deliberately omits idempotency keys:
view adds the key when there is one, plus safe input metadata, the attempt count, the latest failure, and a remote operation ID when server work has already been accepted. Retained private payloads — invitation tokens, avatar bytes, 10DLC campaign text — are redacted.

Statuses

retryable and replayable are different things and need not agree. retryable describes the immediate error. replayable describes whether the saved mutation has a safe replay contract.
resolved appears only in the immediate result of an explicit replay — it is not a journal status or a valid --status filter. Neither is pending; most commands keep polling rather than returning a pending success.

Idempotency keys

Supplied keys are 1–255 printable, non-whitespace ASCII characters unless a command narrows that further. Key matching happens only for keys you supply; a generated key always starts a new record. When an unresolved record matches your key and the request fingerprint agrees, the CLI takes over that record and runs your current input under the original key. Different input is rejected locally:
Successful output never exposes the key. An unresolved error tells you everything you need to recover:

Replay

Replay dispatches a typed operation using the saved input and key. A per-operation lease stops two processes replaying the same record at once. It does not re-read an input file or ask you for the original values.
JSON replay uses a recovery envelope:
A second unresolved failure updates the same record and returns the same operation ID. A final success deletes it.
The CLI never stores or executes shell text. A record holds a typed operation definition, not the command line you typed.

Waiting on server work

Commands never return a successful {"status": "pending"} envelope. They show a spinner in an interactive terminal, stay silent in JSON or redirected output, and keep polling until a terminal result or an interruption. Voice outbound algorithm edits preserve the username and password and use no operation journal or idempotency key. If an edit reports VOICE_OUTBOUND_OPERATION_PENDING, resume the original credential request with its original idempotency key or recovery record, then read the current profile and retry the edit. Voice outbound enable and rotation use the same journal for their optimistic profile version. A same-key continuation of an unresolved request reuses the stored expectedVersion; it does not fetch the changed profile and construct a new request body. Their display-once password is never stored. If the server already applied the mutation, recovery succeeds with password: null and the CLI directs you to rotate with a new key when another password is required. A network or protocol error while polling is not treated as pending — it goes straight to the recovery path. Replay resumes a stored remote operation with a GET and never cancels it. There is no client-side total timeout, so interrupt the process with a signal if you need to stop waiting.

Record lifecycle

The record is durable before the first mutation request. If that write fails, no request is sent at all.
  • A final success deletes the record.
  • A definitive rejection deletes the record and stays an ordinary command error — it never shows up in operation list.
  • Ctrl-C, network interruption, protocol failure, and other ambiguous outcomes keep a record with recovery metadata.
  • One-time service key, API key, webhook-secret, and Voice outbound password output stays in awaiting-output-ack until stdout flushes successfully.
Records expire 30 days after their latest unresolved failure and are cleaned up opportunistically. A cleanup failure after a remote success produces a warning without changing the result.

What is stored

A replayable record keeps only what is needed to rebuild the request: project and resource IDs, normalized change sets, invitation tokens, a private copy of a validated avatar before upload completes, and normalized 10DLC campaign input. Call Registry records keep the automatic-registration boolean or canonical phone-resource IDs — never a profile body or a phone number. Voice outbound records keep the user-facing profile reference, resolved stable profileId, exact expectedVersion, enablement digest algorithm, and rotation mode when present.
Generated one-time secrets are never written to the journal. Service key create, API key create, webhook create, webhook secret rotation, and Voice outbound changes rely on the service’s idempotent response to recover a secret before output is acknowledged.
Records are scoped to the API origin and the authenticated identity, and stored with owner-only permissions in Photon’s configuration directory. Symlinks, non-regular files, unsafe permissions, and a scope mismatch all fail closed.

Unreadable records

Records use only the current strict schema, so an unresolved record written by an earlier incompatible release cannot be read. operation list and ordinary mutations skip unreadable records with a warning so unrelated work continues. operation view and operation replay report OPERATION_JOURNAL_CORRUPT for a record you select directly. operation remove validates only the ID and deletes the file without parsing it, so it can always clear a corrupt record:
operation remove deletes only the local record. It never cancels, reverses, or deletes anything on the server.

Operations saved before an API update

If a saved request lacks newly required fields, replay reports OPERATION_CONTRACT_CHANGED and leaves the record intact. Inspect the remote result before starting a new operation. The CLI cannot safely invent a project slug, API-key permissions, campaign compliance URLs, or a campaign ETag for an old request. An old SMS purchase with a known remote operation ID can still be polled without sending the purchase again. Operations saved before organizations — project member, invitation, and leave operations, and project creation or 10DLC operations without an organization — also report OPERATION_CONTRACT_CHANGED. Check the organization with photon org member list, photon org invitation list, or photon project list before starting a new operation. With --json, errors are written to stderr inside an error object, and the command exits with a nonzero status. Selected fields from an incompatible replay error: