Repeat a call without doing the work twice

PHP

Give a run or a movement of credits a key that names its piece of work, so that a repeat — by the SDK, by your queue or by your own code — is answered instead of done again.

5 min read

A run of a flow and a movement of credits carry an idempotency key. The key names one piece of work. When the same call comes again under the same key, Slen answers it instead of doing the work again — so a call repeated after a timeout, a crash or a retry of your queue is not a second execution, a second spend or a second top-up.

The key the SDK makes and the key you pass

Five calls take a key: operations.execute(), operations.executeAsync(), customers.topUp(), customers.charge() and customers.adjust(). The key is idempotencyKey of the options of the call: ExecuteOptions of a run, MovementOptions of a movement of credits — the same names in both SDKs. When you pass none, the SDK makes a new random key for that call.

  • A key the SDK made covers the SDK's own repeats only. The SDK sends a request again under the same key after no answer, a 429 or a 503 — Retries and timeouts.

  • A call your code makes again — a retry of your queue, a second press of a button — gets a new random key, and a new key is new work: the flow runs again, the credits move again.

  • An empty key you pass is refused before the request is sent, with the code invalid_request. The SDK never puts a key of its own in its place.

Warning:

Pass a key of your own to every run and every movement of credits. Without one, a call your code repeats is done twice.

Choose a key

Make the key from the piece of work, so that every repeat of the work carries the same key and the next piece of work gets a new one: an order and its step (order-42:email), an invoice and its top-up (invoice-7781:topup), or the job of your queue that sends the call.

PHP
use Slen\Sdk\IdempotencyKeys;

// The id of the job of your own queue that sends this request.
$jobId = 'a1f7c2e9';
$key = IdempotencyKeys::forJob($jobId, 'generate_email');

IdempotencyKeys::forJob() makes job:<id>:<operation> — one key for every attempt of the job. The job that runs again gets the first execution back. IdempotencyKeys::generate() makes a random key, the one the SDK makes when you pass none.

  • A key is at most 190 characters. A longer one is refused with 422 before anything runs.

  • A key belongs to the project of the API key. Inside the project, give each piece of work a key of its own.

What a repeat is answered with

PHP
use Slen\Sdk\Modules\Operations\Options\ExecuteOptions;

$input = ['messages' => [['role' => 'user', 'content' => 'Write a short follow-up email.']]];

$execution = $slen->operations()->execute('generate_email', $input, new ExecuteOptions(idempotencyKey: $key));

echo $execution->status->value, PHP_EOL;
PHP
// The queue runs the same job again, so the key is the same.
$again = $slen->operations()->execute('generate_email', $input, new ExecuteOptions(idempotencyKey: $key));

if ($again->replayed) {
    echo "Execution {$again->executionId} ran once. This answer is a replay.", PHP_EOL;
}
CallA repeat under its key
A runThe execution the first request started, as it is at that moment, with replayed set to true. The flow does not run again
A top-up, a charge or an adjustmentThe first answer, for 24 hours. After that, the account as it is now. The credits move once. A refused movement is not kept, and its repeat is a new attempt
  • An execution that is not over is answered with 202 and the status queued, waiting or running — running when the first request is still being walked.

  • An execution that is over is answered with 200 and its outcome. That is so even when the first request ended in an error: the repeat reads the status of the execution and its error_code.

  • cost.actual may be there on a repeat when the first answer had none: the model calls are settled after the execution ends.

The other calls of the SDK take no key. A read changes nothing, and creating a customer, the settings of an account, a block and an unblock leave the same state however many times they are sent. A deletion repeated by the SDK after no answer is done when it finds no customer any more — Retries and timeouts. Idempotency has the same answers in terms of the API.

The same key with another request

PHP
use Slen\Sdk\Exceptions\IdempotencyConflictException;

try {
    $slen->operations()->execute(
        'generate_email',
        ['messages' => [['role' => 'user', 'content' => 'Write a short thank-you email.']]],
        new ExecuteOptions(idempotencyKey: $key),
    );
} catch (IdempotencyConflictException $exception) {
    echo "Refused: {$exception->errorCode}", PHP_EOL;
}

A key that already names a piece of work refuses another one: the API answers 409 with the code idempotency_conflict, nothing runs and nothing moves, and the SDK raises the error.

The exception is IdempotencyConflictException. idempotency_in_progress comes as the same exception: the first request under the key has not finished. Send the call again later, under the same key.

  • For a run, another input, another context, another customer or another mode is another request.

  • For a movement of credits, another customer, credit type, kind or amount is another request, and for the first 24 hours so is another note.

  • An execution that failed keeps its key. To run the work again after you fixed the request, send it under a new key.

Where to go next