GoliathDevelopersGet an API key
Guides / Write without fear6 min read

Write without fear

A write that times out leaves you guessing. Here's how to retry anything and never create it twice.

Why it matters

Did the contact get created? Did the text go out? Retrying without a key duplicates the row, and a send can be delivered twice. Every mutation that honors it accepts an Idempotency-Key header: 1 to 255 characters, and a UUID is ideal. Each operation’s reference page says whether it does.

What happens when a write times out
Your codeGatewayStored resultPOST createContact · key 7c1e…reserve → run → storekey 7c1e…ctc_8f2… · 24 h200 OK · lost in transittimeoutretry · same key 7c1e…stored response200 · Idempotency-Replayed: true
One contact, never two. Press Play to watch the five beats.

How the gateway handles a key

The gateway reserves the key before it runs the operation and stores the response when it finishes. A retry with the same key replays that stored response, marked Idempotency-Replayed: true, so a caller that timed out learns what actually happened. Keys expire after 24 hours.

A key still in flight

If the first request hasn’t finished, a retry gets 409 idempotency_conflict. Wait, then retry with the same key. A new key would run the write a second time.

When a key is handed back

When the gateway can prove a failure happened before the send started (bad variables, a Do-Not-Contact guard), it releases the key, so you can fix the request and retry with the same one.

A retry loop

KEY=$(uuidgen)   # one key per logical write, reused on every retry

for attempt in 1 2 3; do
  # --fail-with-body: a 409, 429 or 5xx is an HTTP failure, to retry with the same key.
  if body=$(curl -sS --fail-with-body https://server.goliathdata.com/api/v1/call \
      -H "Authorization: Bearer $GOLIATH_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $KEY" \
      -d @create-contact.json); then
    # A handled request is HTTP 200 even when the operation failed: read `errors` before calling it done.
    # An operation error (bad input, a guard) is not retryable: the same request would fail the same way.
    if echo "$body" | jq -e '(.errors // []) | length == 0' >/dev/null; then echo "$body"; exit 0; fi
    echo "$body" >&2; exit 1
  fi
  sleep $((attempt * 2))
done
echo "gave up after 3 attempts" >&2
exit 1
// The SDK retries a keyed write itself, with the same key every time.
await goliath.contacts.createContact(
  { input: { sellerFirstName: 'Jane', sellerLastName: 'Doe' } },
  { idempotencyKey: crypto.randomUUID() },
)

Reads refuse the header

A read with an Idempotency-Key gets 400 bad_request instead of having it silently ignored, so the mistake shows up on the first run. In the SDK, passing one on a read is a compile error.

Operations this guide covers: createContact, sendText, createDeal.