API Reference
Idempotency

Idempotency

Idempotency keys let you safely retry API requests without accidentally creating duplicate resources or triggering duplicate actions.


Why Idempotency Matters

Network requests can fail at any point:

  • The request reaches the server and succeeds, but the response is lost in transit
  • The connection times out before the server responds
  • Your client crashes after sending the request

Without idempotency, retrying a POST /environments/{id}/actions/redeploy after a timeout could trigger two deployments. With an idempotency key, the second request returns the same result as the first — no duplicate action.

Idempotency keys are especially important in CI/CD pipelines where flaky networks are common. Add them to every state-changing request.


How It Works

Add the X-Idempotency-Key header to any supported request:

X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

Behavior:

  1. First request — processed normally, result is cached for 24 hours
  2. Same key within 24 hours — cached response is returned immediately, no action is taken
  3. Same key after 24 hours — key is expired, request is treated as new

The cached result is returned with the same HTTP status code as the original request. If the original returned 201 Created, retries also return 201 Created — even though no new resource was created.


Supported Endpoints

EndpointMethodEffect of duplicate
POST /projectsCreate projectReturns existing project
POST /environmentsCreate environmentReturns existing environment
POST /webhooksCreate webhookReturns existing webhook
POST /environments/{id}/actions/deployTrigger deployReturns original job status
POST /environments/{id}/actions/redeployTrigger redeployReturns original job status
POST /environments/{id}/actions/restartRestart environmentReturns original result
POST /environments/{id}/actions/startStart environmentReturns original result
POST /environments/{id}/actions/stopStop environmentReturns original result

GET, PUT, PATCH, and DELETE requests are naturally idempotent and do not require idempotency keys (though they are accepted and ignored).


Key Format

Use a UUID v4 — it's random, globally unique, and well-supported in every language:

# Generate a UUID and use it
KEY=$(uuidgen)
curl -X POST https://api.oec.sh/api/public/v1/environments/env_01abc.../actions/redeploy \
  -H "Authorization: Bearer oec_live_rw_your_key" \
  -H "X-Idempotency-Key: $KEY"

Naming Patterns

PatternExampleGood for
Random UUID550e8400-e29b-41d4-a716-446655440000One-off operations
Resource + commit hashredeploy-abc123def456Git-triggered deploys (same commit = same key)
Resource + timestampredeploy-env_01abc-1741958400Scheduled automation
Pipeline run IDgh-actions-run-12345678CI/CD pipelines
⚠️

Keys must be unique per operation. If you use the same key for two different deployments (e.g., reusing redeploy-env_01abc for every deploy), the second and all subsequent deploys will return the cached result of the first and never actually run. Use a commit hash or timestamp in the key to make it unique per operation.


Safe Retry Pattern

Generate an idempotency key before the request

Generate the key once and reuse it for all retries of the same operation:

import uuid
import time
import requests
 
def deploy_with_retry(session, env_id, max_retries=3):
    # Generate key once — reuse it for all retries
    idempotency_key = str(uuid.uuid4())
    url = f"https://api.oec.sh/api/public/v1/environments/{env_id}/actions/redeploy"
 
    for attempt in range(max_retries):
        try:
            response = session.post(
                url,
                headers={"X-Idempotency-Key": idempotency_key},
                timeout=10,
            )
            response.raise_for_status()
            return response.json()
 
        except requests.exceptions.Timeout:
            if attempt == max_retries - 1:
                raise
            wait = 2 ** attempt  # 1s, 2s, 4s
            print(f"Timeout on attempt {attempt + 1}, retrying in {wait}s...")
            time.sleep(wait)
 
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 409:
                # Action already running — that's fine, return the current status
                return e.response.json()
            raise

Check the response

A 200 or 201 response that was served from cache includes the X-Idempotency-Replayed: true header:

HTTP/1.1 201 Created
X-Idempotency-Replayed: true

This confirms the response is a replay and the original action was not re-executed.


Conflict Responses

If a request with an idempotency key is still in-flight when a duplicate arrives, the API returns:

HTTP/1.1 409 Conflict
{
  "error": "idempotency_key_in_use",
  "message": "A request with this idempotency key is already being processed. Retry after a moment."
}

Wait a few seconds and retry with the same key.