> ## Documentation Index
> Fetch the complete documentation index at: https://nova.dweet.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Safe retry patterns and duplicate submission behavior

Idempotency makes it safe to retry requests without accidentally performing the same operation twice.

Nova Embed supports idempotency in 2 ways:

1. Scoring submissions are idempotent by default.
2. Criteria and library mutations support an optional `Idempotency-Key` header.

Deletion requests are also safe to retry. They deduplicate by deletion scope and identifiers, not by the generic `Idempotency-Key` replay layer.

## Scoring submissions (default)

For `POST /v1/jobs/{jobId}/applications/{applicationId}/scoring-jobs`, Nova deduplicates requests using:

* Your tenant (from `X-Tenant-Id` and your API key)
* `jobId`
* `applicationId`

If you submit the same `jobId` and `applicationId` again, Nova returns the existing scoring job.

If the existing job is already `completed`, use the returned `scoringJob` or fetch it again from the scoring job endpoint later. Nova doesn't start another scoring run or send another `score.completed` webhook for the repeat submission.

This behavior exists even if you do not send an `Idempotency-Key`.

<Note>
  Scoring submission routes do not use the generic HTTP `Idempotency-Key` replay layer. If you send the header there, Nova ignores it and uses scoring's built-in identity rules instead.
</Note>

Batch scoring works similarly. `POST /v1/jobs/{jobId}/scoring-batches` ignores `Idempotency-Key` and deduplicates by the batch's scoring identity: tenant, `jobId`, and the sorted set of `applicationId` values.

If a batch partially accepts valid applications and rejects invalid resume inputs, replaying the same batch identity returns the same `acceptedApplications` and `rejectedApplications`. If you fix a rejected resume later, submit a different batch shape or use the single scoring endpoint.

## Re-scoring

To create a new scoring job for an application, pass `rescore: { enabled: true }`.

Re-scoring is idempotent per criteria version. Submitting `rescore.enabled: true` multiple times without changing criteria returns the same re-score job.

Use re-scoring when you want a fresh run for an application that already has a completed score, for example after changing the criteria version you want to score against.

To pin a specific criteria version:

```json theme={null}
{
  "resume": { "type": "url", "url": "https://storage.example.com/resumes/abc123.pdf" },
  "jobDescription": "We are looking for a Senior Backend Engineer with 5+ years experience...",
  "rescore": {
    "enabled": true,
    "criteriaVersionId": "cmlz26ck2000awp61q9qcjcne"
  }
}
```

## What creates a new scoring job

| Change                                              | New job? |
| --------------------------------------------------- | -------- |
| `jobId` changes                                     | Yes      |
| `applicationId` changes                             | Yes      |
| `rescore.enabled` set to `true`                     | Yes      |
| `rescore.criteriaVersionId` changes while rescoring | Yes      |
| `resume` changes                                    | No       |
| `applicationData` changes                           | No       |

## Retry strategy

1. Retry network failures and `5xx` errors with exponential backoff.
2. For `429 RATE_LIMITED` and retryable `503` responses, wait for `Retry-After` when present before retrying.
3. For scoring retries, re-submit the same `jobId` and `applicationId`, then follow the scoring submission behavior above. Use `rescore` only when you intentionally want a new scoring job.

## Idempotency-Key header (criteria and library mutations)

For criteria and library write requests, you can send an `Idempotency-Key` header to safely retry without duplicating side effects.

Supported methods: `POST`, `PUT`, `PATCH`, `DELETE`.

### How it works

When you send `Idempotency-Key: <key>`:

* The first request runs normally.
* A retry with the same key and the same request parameters returns the original response and includes `Idempotent-Replayed: true`.
* Reusing the same key with different request parameters returns `422 IDEMPOTENCY_KEY_ALREADY_USED`.
* If another request with the same key is still running, the API returns `409 IDEMPOTENCY_REQUEST_IN_PROGRESS`.

<Tip>
  Use a UUID v4 for `Idempotency-Key`. Keys must be 1 to 255 characters.
</Tip>

<Note>
  Idempotency keys stay valid for 24 hours. After that, you can reuse the same key and Nova treats it as a new request.
</Note>

<Note>
  `Idempotency-Key` replay and mismatch behavior does not apply to scoring submissions or scoring batches. Those endpoints already deduplicate by scoring identity.
</Note>

<Note>
  Deletion request endpoints do not use the generic `Idempotency-Key` replay layer. Repeating the same application or tenant deletion request returns the existing deletion request where possible.
</Note>

<Note>
  Idempotency keys are scoped to your partner account and environment. All active API keys under the same partner share the same idempotency namespace for that environment. Reusing the same key across different `X-Tenant-Id` values is treated as a parameter mismatch and returns `422 IDEMPOTENCY_KEY_ALREADY_USED`.
</Note>

### Which responses are replayed

* Successful `2xx` responses are replayed with the same key.
* Endpoint-generated `4xx` responses are replayed with the same key, including business-rule failures and handler-thrown validation errors.
* `409 IDEMPOTENCY_REQUEST_IN_PROGRESS` and `429 RATE_LIMITED` are not cached. Retry the same request with the same key after the condition clears.
* Request-shape failures are not cached. That includes missing required headers, invalid `Content-Type`, malformed JSON, and schema or path validation errors. After you fix the request, you can retry with the same key.

### Rate limiting and retries

Rate limiting (including in-flight concurrency limits) runs before the HTTP idempotency replay layer.

<Note>
  This means a retry can return `429 RATE_LIMITED` before the API has a chance to replay a cached response. If you get `429 RATE_LIMITED`, wait for `Retry-After` and retry again using the same `Idempotency-Key`.
</Note>

### Example

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://embed.nova.dweet.com/v1/jobs/job-123/criteria/items \
    -H "Authorization: Bearer sk_live_..." \
    -H "X-Tenant-Id: acme-corp" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 2d6d8d5a-6c4f-4c2f-8c6e-5b6f0d51a1b2" \
    -d '{"text":"5+ years backend experience","importance":"required"}'
  ```
</RequestExample>

```typescript SDK theme={null}
await nova.jobs.criteria.items.add(
  {
    jobId: 'job-123',
    body: {
      text: '5+ years backend experience',
      importance: 'required',
    },
  },
  {
    idempotencyKey: '2d6d8d5a-6c4f-4c2f-8c6e-5b6f0d51a1b2',
  },
);
```

<Tip>
  If a scoring request fails because the resume URL is inaccessible, generate a new URL and retry the same scoring submission. Since the job wasn't created, the retry is safe.
</Tip>
