A create request timed out: how to retry without creating duplicates

Edited

When you create a record (an invoice, a bill, a journal entry) and the request times out, you can't tell from the error alone whether the provider created it. If you just send it again, you may end up with two records in your customer's accounting system. This article explains how to retry safely.

How to recognise an uncertain create

Apideck waits up to 28 seconds for the provider to respond. If the provider doesn't answer in time, or the connection drops, Apideck returns a 504 (timeout) or 502 (connection dropped).

For create requests, the error tells you that the result is unknown:

{
  "status_code": 504,
  "type_name": "ConnectorExecutionError",
  "detail": {
    "retriable": false,
    "outcome": "uncertain"
  }
}

(The example is abbreviated. The full error includes more fields.)

  • outcome: "uncertain" means the provider may or may not have created the record.

  • retriable: false means don't retry blindly. Retry with an idempotency key, or check first.

Apideck never retries a create on its own. Each create is sent to the provider exactly once, so a duplicate can only come from a retry on your side.

The easiest way: send an Idempotency-Key

For the operations below, you can add an Idempotency-Key header to the create. If it times out or fails with an uncertain outcome, resend the same request with the same key, and the record is created at most once.

POST /accounting/invoices
Idempotency-Key: 8f14e45f-ceea-467a-9a7c-3c8e5d2b1a90

Connector

Operations

Key remembered for

QuickBooks Online

Journal entry and invoice creates, invoice updates

24 hours

Intuit Enterprise Suite

Journal entry and invoice creates, invoice updates

24 hours

NetSuite

Journal entry creates

24 hours

Sage Intacct

Journal entry, invoice and credit note creates

24 hours

Sage Intacct REST

Journal entry, invoice and credit note creates

1 hour

Xero

Bill creates

6 minutes

How it works:

  • Generate a new key for each record you create, for example a UUID. A key is 1 to 255 printable characters.

  • Retry within the window. After the time in the table, the same key counts as a new request, so retry promptly. This matters most for Xero, where the window is only 6 minutes.

  • With a key, a timed-out create is safe to retry. The error then shows retriable: true.

What the responses mean:

Response

Meaning

409

The first attempt with this key is still running. Wait a moment and retry.

422

You reused the key with a different body. Each key belongs to one request.

400

This connector or operation doesn't support the key. Nothing was created. Retry without the header and use the steps below.

Two more things to know:

  • A key stays tied to its first request, even if that request was rejected. If you fix a rejected request, send it with a new key.

  • Some connectors repeat the original error to retries with the same key. If retries keep returning the same error, check whether the record exists before sending it again with a new key.

Without an idempotency key

For other connectors and record types, use your own identifier to check before retrying:

  1. Give every record your own unique identifier before you send it. Usually that's the document number or reference (see below).

  2. Send the create.

  3. On a timeout, a dropped connection or any 5xx, look the record up by that identifier.

  4. If it's there, the create succeeded. Take the id from the lookup and carry on.

  5. If it's not there, it's safe to retry the create.

Treat a 500, 502 or 503 on a create the same way, even when the response has no outcome field. The provider may still have saved the record.

How to look a record up, by provider

QuickBooks Online

  • Invoices: GET /accounting/invoices?filter[number]=INV-1001

  • Journal entries: GET /accounting/journal-entries?filter[number]=JE-1001

  • QuickBooks limits number to 21 characters. Longer values are rejected, not shortened.

  • QuickBooks doesn't reject a duplicate number on journal entries (with default company settings), so making each number unique is up to you.

Xero

  • Invoices: GET /accounting/invoices?filter[number]=INV-1001

  • Bills: GET /accounting/bills?filter[bill_number]=BILL-1001, or filter[reference]=…

  • Xero allows duplicate bill numbers, so a lookup can return more than one bill. Pick the one that matches your other details.

NetSuite

  • Set reference on the record. NetSuite stores it as its External ID and requires it to be unique.

  • If you retry with the same reference and the first attempt did succeed, NetSuite rejects the retry with "This record already exists" instead of creating a duplicate. That error tells you the original create went through.

  • This applies to invoices, bills, payments, bill payments, credit notes, refunds and expenses.

  • Invoices can also be looked up with GET /accounting/invoices?filter[number]=….

Sage Intacct REST

  • Invoices and credit notes: look up by filter[number]=….

  • Journal entries: number can't be set when you create one, because Sage assigns it when the entry is posted. Use source_id as your own identifier instead. To check whether a create landed, list recent journal entries with filter[updated_since] and match on source_id.

Other providers

If the resource supports a number or reference filter, use it. You can see which filters each connector supports in the connector coverage. Otherwise, list recently created records with filter[updated_since] set to just before your create, and match on your own identifier.

Keep your own timeout above 30 seconds

Set your HTTP client's timeout above 30 seconds. Then you receive Apideck's answer, including the outcome information, instead of timing out first.

Related

Was this article helpful?

Sorry about that! Care to tell us more?

Thanks for the feedback!

There was an issue submitting your feedback
Please check your connection and try again.