Idempotency
Networks fail. When a purchase request times out you cannot tell whether CELITECH received it, and sending it again could buy a second eSIM. Idempotency keys make these retries safe: send the same key with the same request and CELITECH performs the action once, then returns the original result to every repeat.
Using Idempotency Keys
Generate a unique key (a UUID works well) for each purchase you intend to make, and pass it with the request. Keys are supported on every request that creates or changes something (the API Reference marks them with the Idempotency-Key parameter): creating a purchase (V2 and the original endpoint), topping up an eSIM, editing a purchase, and suspending an eSIM. They are optional: without a key, requests behave exactly as before.
- JavaScript
- TypeScript
- Python
- PHP
- Java
- Go
- C#
- Ruby
import { Celitech } from 'celitech-sdk';
import { randomUUID } from 'crypto';
(async () => {
const celitech = new Celitech({
clientId: 'CLIENT_ID',
clientSecret: 'CLIENT_SECRET',
});
const createPurchaseV2Request = {
destination: 'FRA',
dataLimitInGb: 1,
duration: 7,
quantity: 1,
};
const idempotencyKey = randomUUID(); // one key per purchase
const { data } = await celitech.purchases.createPurchaseV2(createPurchaseV2Request, {
idempotencyKey,
});
console.log(data);
})();
import { Celitech, CreatePurchaseV2Request } from 'celitech-sdk';
import { randomUUID } from 'crypto';
(async () => {
const celitech = new Celitech({
clientId: 'CLIENT_ID',
clientSecret: 'CLIENT_SECRET',
});
const createPurchaseV2Request: CreatePurchaseV2Request = {
destination: 'FRA',
dataLimitInGb: 1,
duration: 7,
quantity: 1,
};
const idempotencyKey = randomUUID(); // one key per purchase
const { data } = await celitech.purchases.createPurchaseV2(createPurchaseV2Request, {
idempotencyKey,
});
console.log(data);
})();
import uuid
from celitech import Celitech
from celitech.models import CreatePurchaseV2Request
sdk = Celitech(
client_id="CLIENT_ID",
client_secret="CLIENT_SECRET"
)
request_body = CreatePurchaseV2Request(
destination="FRA",
data_limit_in_gb=1,
duration=7,
quantity=1
)
idempotency_key = str(uuid.uuid4()) # one key per purchase
result = sdk.purchases.create_purchase_v2(
request_body=request_body,
idempotency_key=idempotency_key
)
print(result)
use Celitech\Client;
use Celitech\Models\CreatePurchaseV2Request;
$sdk = new Client(clientId: 'CLIENT_ID', clientSecret: 'CLIENT_SECRET');
$input = new Models\CreatePurchaseV2Request(
destination: "FRA",
dataLimitInGb: 1,
duration: 7,
quantity: 1
);
$idempotencyKey = bin2hex(random_bytes(16)); // one key per purchase
$response = $sdk->purchases->createPurchaseV2(
input: $input,
idempotencyKey: $idempotencyKey
);
print_r($response);
import io.github.celitech.celitechsdk.Celitech;
import io.github.celitech.celitechsdk.config.CelitechConfig;
import io.github.celitech.celitechsdk.models.CreatePurchaseV2OkResponse;
import io.github.celitech.celitechsdk.models.CreatePurchaseV2Request;
import java.util.List;
import java.util.UUID;
public class Main {
public static void main(String[] args) {
CelitechConfig config = CelitechConfig.builder()
.clientId("CLIENT_ID")
.clientSecret("CLIENT_SECRET")
.build();
Celitech celitech = new Celitech(config);
CreatePurchaseV2Request createPurchaseV2Request = CreatePurchaseV2Request.builder()
.destination("FRA")
.dataLimitInGb(1D)
.duration(7D)
.quantity(1D)
.build();
String idempotencyKey = UUID.randomUUID().toString(); // one key per purchase
List<CreatePurchaseV2OkResponse> response =
celitech.purchases.createPurchaseV2(createPurchaseV2Request, idempotencyKey);
System.out.println(response);
}
}
import (
"context"
"fmt"
"github.com/Celitech/CelitechSDKGo/pkg/celitech"
"github.com/Celitech/CelitechSDKGo/pkg/celitechconfig"
"github.com/Celitech/CelitechSDKGo/pkg/purchases"
"github.com/Celitech/CelitechSDKGo/pkg/util"
"github.com/google/uuid"
)
config := celitechconfig.NewConfig()
config.SetClientId("CLIENT_ID")
config.SetClientSecret("CLIENT_SECRET")
client := celitech.NewCelitech(config)
request := purchases.CreatePurchaseV2Request{
Destination: util.ToPointer("FRA"),
DataLimitInGb: util.ToPointer(float64(1)),
Duration: util.ToPointer(float64(7)),
Quantity: util.ToPointer(float64(1)),
}
idempotencyKey := uuid.NewString() // one key per purchase
response, err := client.Purchases.CreatePurchaseV2(
context.Background(),
request,
purchases.WithIdempotencyKey(idempotencyKey),
)
if err != nil {
panic(err)
}
fmt.Println(response)
using Celitech.SDK;
using Celitech.SDK.Config;
using Celitech.SDK.Models;
var config = new CelitechConfig {
ClientId = "CLIENT_ID",
ClientSecret = "CLIENT_SECRET"
};
var client = new CelitechClient(config);
var input = new CreatePurchaseV2Request {
Destination = "FRA",
DataLimitInGb = 1,
Duration = 7,
Quantity = 1
};
var idempotencyKey = Guid.NewGuid().ToString(); // one key per purchase
var response = await client.Purchases.CreatePurchaseV2Async(input, idempotencyKey: idempotencyKey);
Console.WriteLine(response);
require 'securerandom'
require 'celitech'
sdk = Celitech::Client.new(client_id: 'CLIENT_ID', client_secret: 'CLIENT_SECRET')
request_body = Celitech::Models::CreatePurchaseV2Request.new(
destination: 'FRA',
data_limit_in_gb: 1,
duration: 7,
quantity: 1
)
idempotency_key = SecureRandom.uuid # one key per purchase
response = sdk.purchases.create_purchase_v2(
body: request_body,
config: Celitech::Config.new(additional_headers: { 'Idempotency-Key' => idempotency_key })
)
puts response
Calling the REST API directly? Send the same value in an Idempotency-Key request header. See the API Reference.
What Happens When You Retry
| Situation | Result |
|---|---|
| First request with a key | Executed normally. |
| Same key and same request, original succeeded | The original result is returned (same ICCID and activation details). Nothing is purchased again. Direct REST callers also see the response header Idempotent-Replayed: true. |
| Same key, original still being processed | 409 Conflict with a Retry-After header. Wait that many seconds and retry. |
| Same key, different request | 422 Unprocessable Entity. |
| Malformed key | 400 Bad Request. |
| Idempotency service temporarily unavailable | 503 Service Unavailable. The request was not processed; retry it. |
Rules for Keys
- Use 1 to 255 printable ASCII characters, without spaces. A random UUID is a good choice.
- Generate one key per purchase you intend to make. Reuse a key only to retry that exact request. A new purchase needs a new key.
- A request is "the same" when the operation and its parameters match. The order of fields does not matter.
- Keys belong to your credentials, so other accounts can never collide with yours.
- Keys are remembered for 3 days. After that, the key can be used again.
Retrying Safely
Reuse the same key for every attempt of the same purchase, including attempts you make yourself after a timeout, a connection error, or a 5xx/503 response. On a 409, wait the number of seconds in Retry-After first.
Only successful responses are remembered. If a request returns an error such as 400, retrying it with the same key runs it again, so you can fix the problem and resend. If you need to be certain that nothing was created by a failed attempt, check your purchases (for example by the referenceId you sent) before starting over with a new key.
A 422 means the same key was used for two different requests. This is almost always a bug in how keys are generated: create a fresh key for each new purchase.