External Reviewer Suggestions via Relay API
Overview
The Relay API supports submission and management of third-party reviewer suggestions for manuscripts in ScholarOne Manuscripts. It allows integrated reviewer-matching providers to push their recommendations directly into the editor's reviewer selection workflow, so editors can evaluate, compare, and invite suggested reviewers without leaving ScholarOne.
Using a single endpoint, a provider can:
- Submit a batch of suggested reviewers (candidates) for a specific manuscript - for example, when the provider's matching engine first produces recommendations.
- Add further candidates to an existing batch over time - for example, when a user hand-picks an additional reviewer in the provider's interface.
- Update details of previously sent candidates - for example, refreshed relevance scores or match explanations.
- Delete candidates that should no longer be shown to editors.
Prerequisites
- The Relay feature must be enabled for the target site.
- The provider must be registered and active in the ScholarOne provider registry. Requests from unregistered or disabled providers are rejected.
- The API account provisioned by ScholarOne must be entitled to the
addJSONDataoperation and to the target site.
Method: POST
Resource
/api/s1m/v2/system/addJSONData
Terminology
| Term | Meaning |
|---|---|
| Manuscript | The paper under review in ScholarOne. Always identified by documentId. |
Suggestion batch (submissionId) | One batch of reviewer suggestions sent by a provider for one manuscript. Identified by the combination of provider + submissionId (the provider's own ID, e.g. GC-2026-001). This is what the submit operation creates and what add/update/delete target. |
| Candidate | One suggested reviewer inside a batch. Identified within the batch by providerCandidateId (the provider's own ID). ScholarOne internal IDs are never exposed or required. |
| Provider | The integrated third-party system. The provider identifier is assigned during onboarding. |
A manuscript can accumulate multiple suggestion batches, from different providers, or several from the same provider over time.
Request Parameters
| Element | Type | Description | Required | Example or Default Value |
|---|---|---|---|---|
| addJSONData | Root | Yes | ||
| username | String | Profile User Name. This is the user name used by the API, not a ScholarOne Manuscripts user. This is from the Caller's profile. | Yes | sample_user |
| password | String | The API Key. Encrypted value uniquely identifying and authenticating Caller | Yes | SRU4DQ5WOJ2PX8CA |
| site_name | String | Site short name. The short name is the abbreviated or truncated name of the journal, society, publisher, or family. | Yes | salesdemoplus |
| url | String | The Web Service URL, which identifies the specific service to handle the request. | Yes | v2/system/addJSONData |
| external_id | String | An id value that can be set by the client for call tracking. Caller supplied Text string to be stored with Audit History information. | No | 123456 |
Authentication and routing notes
- Authentication uses HTTP Digest (realm
S1ApiService) with the API account ScholarOne provisions for the provider.- The
Hostheader must match the target environment's domain. The v2 endpoints resolve the environment from theHostheader, not only from theenvironment_namequery parameter.- Requests for a site the account is not entitled to are rejected.
Request Body Elements
- The request body must be in JSON format.
- The reviewer suggestion payload is wrapped in a
dataroot element, with the processor type as a string ("4") and the payload nested underpayload:
JSON
{
"data": {
"type": "4",
"payload": {
...reviewer suggestion payload...
}
}
} - Every payload requires an
operationfield:"submit","add","update", or"delete". Unknown values are rejected. - The
reviewersfield is always an array. A single entry is fine, and a bare object is also accepted anywhere an array is expected.
| Operation | Purpose |
|---|---|
| submit | Creates a new suggestion batch for a manuscript with its initial set of candidates. Use once per batch, e.g. when your system first produces recommendations. |
| add | Appends candidates to an existing batch (e.g. a user hand-picks an additional candidate). The batch must already exist, or the request fails with "Submission not found". |
| update | Modifies fields on existing candidates in a batch. Send only the fields being changed, plus providerCandidateId. |
| delete | Removes candidates from a batch (soft delete, see Delete Behavior below). |
🔄 [TEMP - finalize before release] Note: All four operations require the target manuscript to be in an active peer-review state - its current version must be submitted, revised, or resubmitted, and not in the author's draft. Requests against manuscripts outside these states are rejected at the request level. See Request-Level Failure below.
Operation: submit - Creates a Batch
Request Body Elements for submit example
JSON
{
"data": {
"type": "4",
"payload": {
"operation": "submit",
"provider": "GlobalCampus" or "Prophy" ,
"providerVersion": "2.1",
"submissionId": "WRK-2026-001",
"documentId": 12345,
"timestamp": "2026-02-25T10:30:00Z",
"reviewers": [
{
"providerCandidateId": "gc-001",
"firstName": "Jane",
"lastName": "Doe",
"email": "[email protected]",
"orcid": "0000-0001-2345-6789",
"relevanceScore": 95,
"matchReason": "Expert in quantum computing, 7 related publications",
"matchDetails": "<ul><li><a href=\"https://doi.org/10.1136/example\">Paper title</a></li></ul>"
}
]
}
}
}Request Body Elements Description - submit
| Request Body Elements/Example | Type | Description | Req |
|---|---|---|---|
| data | Object | Root object that defines the request structure | Yes |
type"4" | String | This field specifies the request type: set "4" to submit Reviewer Suggestions. | Yes |
| payload | Object | Contains the specific details relevant to the request. | Yes |
operation"submit" | String | The operation to perform. Must be "submit" to create a new suggestion batch. | Yes |
provider"GlobalCampus"or "Prophy" | String | Provider identifier, assigned during onboarding. Must be registered and active in the ScholarOne provider registry. | Yes |
providerVersion"2.1" | String | Version of the provider software producing the suggestions. Used for submit only. | Yes |
submissionId"WRK-2026-001" | String | The provider's unique ID for this suggestion batch. Together with the provider, identifies the batch for later add/update/delete operations and retry handling. | Yes |
documentId12345 | Integer | Unique identifier of the manuscript in ScholarOne. Must belong to the target site. | Yes |
timestamp"2026-02-25T10:30:00Z" | String | ISO 8601 instant with offset. Informational; not used to order or reconcile operations. | Yes |
| reviewers | Array | Array of candidate objects - the suggested reviewers in this batch. A single entry is accepted. Must not be empty. See Candidate Field Reference for the fields of each candidate object. | Yes |
For candidate-level fields inside reviewers (names, email, ORCID, relevance score, match explanations, metrics), see the Candidate Field Reference section below. On submit, the required candidate fields are providerCandidateId, firstName, lastName, and email.
Operation: add - Appends Candidates to an Existing Batch
Request Body Elements for add example
JSON
{
"data": {
"type": "4",
"payload": {
"operation": "add",
"provider": "GlobalCampus" or "Prophy",
"submissionId": "WRK-2026-001",
"timestamp": "2026-02-26T09:15:00Z",
"reviewers": [
{
"providerCandidateId": "gc-003",
"firstName": "Maria",
"lastName": "Garcia",
"email": "[email protected]",
"relevanceScore": 91,
"matchReason": "Hand-picked by the requesting user"
}
]
}
}
}Request Body Elements Description - add
| Request Body Elements/Example | Type | Description | Req |
|---|---|---|---|
| data | Object | Root object that defines the request structure | Yes |
type"4" | String | This field specifies the request type: set "4" to submit Reviewer Suggestions. | Yes |
| payload | Object | Contains the specific details relevant to the request. | Yes |
operation"add" | String | The operation to perform. Must be "add" to append candidates to an existing batch. The batch must already exist, or the request fails with "Submission not found". | Yes |
provider"GlobalCampus" or "Prophy" | String | Provider identifier. Must match the batch being targeted. | Yes |
submissionId"WRK-2026-001" | String | The provider's ID for the batch being targeted. Must match an existing batch for this provider. | Yes |
timestamp"2026-02-26T09:15:00Z" | String | ISO 8601 instant with offset. Accepted but not validated on add; sending it is recommended. | No |
| reviewers | Array | Array of candidate objects to append to the batch. A single entry is accepted. Must not be empty. | Yes |
Candidate-level requirements on add are identical to submit: providerCandidateId, firstName, lastName, and email are required; all other fields in the Candidate Field Reference are optional. If a providerCandidateId already exists as a live record in the batch, that entry is skipped and audited (the existing record is not modified - use update for that).
Operation: update - Modifies Existing Candidates
Request Body Elements for update example
JSON
{
"data": {
"type": "4",
"payload": {
"operation": "update",
"provider": "GlobalCampus" or "Prophy",
"submissionId": "WRK-2026-001",
"timestamp": "2026-02-27T14:00:00Z",
"reviewers": [
{
"providerCandidateId": "gc-003",
"relevanceScore": 90,
"matchReason": "Updated after full-text analysis"
}
]
}
}
}Request Body Elements Description - update
| Request Body Elements/Example | Type | Description | Req |
|---|---|---|---|
| data | Object | Root object that defines the request structure | Yes |
type"4" | String | This field specifies the request type: set "4" to submit Reviewer Suggestions. | Yes |
| payload | Object | Contains the specific details relevant to the request. | Yes |
operation"update" | String | The operation to perform. Must be "update" to modify fields on existing candidates in a batch. | Yes |
provider"GlobalCampus" or "Prophy" | String | Provider identifier. Must match the batch being targeted. | Yes |
submissionId"WRK-2026-001" | String | The provider's ID for the batch being targeted. Must match an existing batch for this provider. | Yes |
timestamp"2026-02-27T14:00:00Z" | String | ISO 8601 instant with offset. Accepted but not validated on update; sending it is recommended. | No |
| reviewers | Array | Array of candidate objects to update. Each entry must include providerCandidateId plus only the fields being changed; omitted fields are left untouched. | Yes |
reviewers[].providerCandidateId"gc-003" | String | The provider's ID of the existing candidate to modify. If no live record matches, that entry is skipped and audited; the request still returns SUCCESS. | Yes |
Any field from the Candidate Field Reference may be sent alongside providerCandidateId to change it. Note: sending personId on update re-validates and re-links the person record, but changing email or orcid alone does not re-trigger automated person matching.
Operation: delete - Removes Candidates from a Batch
Request Body Elements for delete example
JSON
{
"data": {
"type": "4",
"payload": {
"operation": "delete",
"provider": "GlobalCampus" or "Prophy",
"submissionId": "WRK-2026-001",
"timestamp": "2026-02-28T08:45:00Z",
"reviewers": [
{
"providerCandidateId": "gc-002"
}
]
}
}
}Request Body Elements Description - delete
| Request Body Elements/Example | Type | Description | Req |
|---|---|---|---|
| data | Object | Root object that defines the request structure | Yes |
type"4" | String | This field specifies the request type: set "4" to submit Reviewer Suggestions. | Yes |
| payload | Object | Contains the specific details relevant to the request. | Yes |
operation"delete" | String | The operation to perform. Must be "delete" to remove candidates from a batch. Delete is a soft delete, see Delete Behavior below. | Yes |
provider"GlobalCampus" or "Prophy" | String | Provider identifier. Must match the batch being targeted. | Yes |
submissionId"WRK-2026-001" | String | The provider's ID for the batch being targeted. Must match an existing batch for this provider. | Yes |
timestamp"2026-02-28T08:45:00Z" | String | ISO 8601 instant with offset. Accepted but not validated on delete; sending it is recommended. | No |
| reviewers | Array | Array of candidate objects identifying the records to remove. Only providerCandidateId is used; any other fields are ignored. | Yes |
reviewers[].providerCandidateId"gc-002" | String | The provider's ID of the existing candidate to remove. If no live record matches, that entry is skipped and audited; the request still returns SUCCESS. | Yes |
Candidate Field Reference
The fields below apply to each object inside the reviewers array.
| Field | Req* | Type | Limit | On Oversized/Invalid | Notes |
|---|---|---|---|---|---|
| providerCandidateId | Yes | String | 100 chars | Truncated | Provider's unique ID within the batch |
| firstName | Yes | String | 100 chars | Truncated | |
| lastName | Yes | String | 100 chars | Truncated | |
| Yes | String | 255 chars | Truncated; invalid format → candidate rejected | Stored lowercased; used for automated person matching | |
| displayName | No | String | 200 chars | Truncated | e.g. "Dr. Jane Doe" |
| orcid | No | String | 19 chars stored | Invalid format → candidate rejected | URL form accepted, normalized to bare form (see ORCID Format) |
| institution | No | String | 255 chars | Truncated | |
| country | No | String | 2 chars |
🔄 [TEMP - finalize before release] Non-two-letter value → candidate rejected |
|
| relevanceScore | No | Integer | 0–100 | Out of range → candidate rejected | Null sorts last (see Display Order) |
| personId | No | Integer | - | Never causes rejection (see Person Matching) | Optional ScholarOne person hint |
| matchReason | No | String | 2,000 chars | Over limit or disallowed HTML → candidate rejected | Short summary, shown in list view |
| matchDetails | No | String | 4,000 chars | Over limit or disallowed HTML → candidate rejected | Rich HTML, shown in expanded detail view |
| metrics | No | Array | 1,000 chars** | Over limit → candidate rejected | Provider-defined metrics |
| flags | No | Array | 1,000 chars** | Over limit → candidate rejected | Provider-defined flags |
| providerData | No | Array | 1,000 chars** | Over limit → candidate rejected | Additional provider data |
* "Required" applies to submit and add. For update and delete, only providerCandidateId is required.
** Limit applies to the serialized JSON of each field independently, measured in both characters and UTF-8 bytes (the stricter applies).
"Candidate rejected" means that one candidate is skipped and the reason is written to the manuscript's audit trail; the rest of the batch still processes (see Per-Candidate Failures).
Person Matching
When personId is not provided, ScholarOne automatically matches each candidate to its user records: first by email address, then by ORCID, scoped to the journal's configuration family.
When personId is provided:
- If the person exists, belongs to the target site's configuration family, and does not conflict with the email/ORCID match, it is used directly (bypassing automated matching).
- If the email/ORCID resolves to a different person, the automated match wins, and the conflict is written to the manuscript's audit trail.
- If the person is not found, or does not belong to the site's configuration family, ScholarOne falls back to automated matching and logs the issue.
🔄 [TEMP - finalize before release] A bad optional personId hint never costs the editor a suggestion - the candidate is still saved. (One exception, unrelated to the hint: if the candidate's email or ORCID matches more than one un-merged ScholarOne account, the candidate cannot be resolved to a single person and is skipped and audited - see Per-Candidate Failures.)
On update: sending personId re-validates and re-links the record. Changing email or orcid alone does not re-trigger automated matching.
ORCID Format
Both forms are accepted on input and normalized to the bare form for storage:
- Bare:
0000-0001-2345-6789 -
🔄 [TEMP - finalize before release] URL form: http://orcid.org/0000-0001-2345-6789 or https://orcid.org/0000-0001-2345-6789 - both prefixes are accepted and stripped during normalization.
After normalization the value must match 0000-0000-0000-000X (the final character may be X); otherwise the candidate is rejected.
Metrics and Flags
The metrics and flagsfields accept an array of JSON objects with free-form keys. There are no reserved or recognized keys; content is stored and displayed as-is. All of the following are accepted and equivalent:
JSON
"metrics": [{"hIndex": 24, "citationCount": 3456}]
"metrics": [{"hIndex": 24}, {"citationCount": 3456}]
"metrics": {"hIndex": 24}
Display noteThe editor interface derives its metric columns from the keys present in
metrics, so consistent key naming across candidates produces a clean comparison table. Candidates lacking a key show a blank cell.
Provider Data: Recognized Keys (relevantWorks / relatedWorks)
relevantWorks / relatedWorks)Within providerData, two keys are recognized specially by the reviewer suggestion interface: relevantWorks and relatedWorks. When either key is present, its contents render in the candidate's detail view as a titled list of links, labeled "Most Relevant Works" instead of being shown as generic, unformatted detail.
Each entry in a relevantWorks / relatedWorks array is an object describing one linked item:
| Field | Type | Description | Req |
|---|---|---|---|
| title or name | String | The link's display text. title is checked first; name is used if title is absent. | Yes |
| detailUrl, url, or link | String | The link's destination. Checked in that order, the first one present is used. | Yes |
If an item is missing both a recognized title field and a recognized URL field, it does not render as a link.
JSON
"providerData": [
{
"relevantWorks": [
{ "title": "Machine Learning Approaches in Peer Review", "detailUrl": "https://provider.example.com/works/12345" },
{ "title": "Bias Detection in Reviewer Matching Systems", "detailUrl": "https://provider.example.com/works/12346" }
]
},
{
"expertiseArea": "Natural Language Processing"
}
]In this example, relevantWorks renders as a two-item "Most Relevant Works" list with working links, while expertiseArea not a recognized key, displays as plain, unformatted detail alongside it.
relatedWorks is accepted as an alias for relevantWorks and behaves identically; use whichever name best matches your own data model.
Allowed HTML (matchReason, matchDetails)
Accepted tags:
a, abbr, b, blockquote, br, cite, code, dd, div, dl, dt, em, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, q, small, span, strike, strong, sub, sup, table, tbody, td, th, thead, tr, u, ul
Attribute rules:
a:hrefonly, and it must use thehttp://orhttps://scheme.img:src(must usehttps://) andalt(always allowed, for accessibility).- All other tags, including
divandspan: no attributes. CSS classes and inline styles are not accepted; visual presentation is controlled by ScholarOne.
Rejected, not sanitizedContent containing a disallowed tag or attribute is rejected - the candidate record is not saved and the issue is written to the manuscript's audit trail. ScholarOne does not strip or alter provider content; it must meet the allowlist as submitted.
Character limits count the full content including markup, attribute values, and entity references. As sizing guidance: ten DOI links in list format consume roughly 3,500 of 'matchDetails' 4,000 characters.
Responses
All responses use the standard ScholarOne API envelope. Results are reported at the request level; there is no per-candidate detail in the response.
Success - HTTP 200
{
"Response": {
"status": "SUCCESS",
"callId": "...",
"result": {
"returnCode": "OK",
"type": 4
}
}
}The request was accepted and processed. Individual candidates may still have been skipped or rejected (see Per-Candidate Failures).
Request-Level Failure - HTTP 400
{
"Response": {
"status": "FAILURE",
"callId": "...",
"errorDetails": {
"errorCode": 100,
"moreInfo": {
"errors": {
"errorCode": 100,
"errorMessage": "Duplicate submission already processed: GC-2026-001"
}
},
"userMessage": "An unexpected error has occurred"
}
}
}The entire request was rejected and nothing was stored. The underlying reason is in errorDetails.moreInfo.errors.errorMessage. Request-level failures include:
- Missing/unknown
operation, missing required batch-level fields, malformedtimestamp documentIdnot found or not belonging to the caller's site (submit)-
🔄 [TEMP - finalize before release] The target manuscript is not in an active peer-review state. Suggestions are accepted only for a manuscript whose current version is submitted, revised, or resubmitted and is not in the author's draft. Manuscripts in draft (including a revised/resubmitted version still in the author's hands), in appeal, or in any terminal state (decisioned, accepted, withdrawn, …) are rejected. Applies to all four operations. The rejection message is: "Document
is not accepting reviewer suggestions: the manuscript must be a submitted, revised, or resubmitted version that is not in draft." - Batch not found for provider +
submissionId(add/update/delete) - Duplicate fully-processed batch on
submitretry (see Idempotency) - Provider not active, or the Relay feature disabled for the site
- Empty
reviewersarray -
🔄 [TEMP - finalize before release] reviewers array exceeding twice the configured per-document maximum (more than 200 candidates at the default maximum of 100) - see Candidate Limits
-
🔄 [TEMP - finalize before release] ScholarOne is unable to determine the provider's current live candidate count for the manuscript (a data-tier error); the request is rejected rather than risk exceeding the limit
Per-Candidate Failures - Audited, Not Returned
Within an otherwise valid request, individual candidates can be skipped (duplicate ID, over the per-request cap) or rejected (validation failure: disallowed HTML, bad ORCID, bad country, missing required fields, oversized JSON fields). The remaining candidates are still committed. Each skip/rejection is written to the manuscript's audit trail in ScholarOne with the provider, submissionId, providerCandidateId, and reason.
🔄 [TEMP - finalize before release] Ambiguous person match: if a candidate's email or ORCID matches more than one un-merged ScholarOne account, the candidate cannot be resolved to a single person and is skipped and audited. Resolve the duplicate with the user-administration / merge tools, then re-send the candidate.
The response does not enumerate these. Providers who need to verify ingestion can coordinate with the journal team, who can read the audit trail and the suggestion list in the editor interface. A batch that loses candidates this way is marked partial, and a partial batch can be wholly replaced by re-submitting the same submissionId (see Idempotency, case c).
Batch Lifecycle
Statuses
| Status | Meaning |
|---|---|
| 1 - received | Batch row created, processing not finished |
| 2 - processed | All candidates in the batch were stored |
| 3 - partial | Some candidates stored; the rest were skipped/rejected and audited |
| 4 - failed | No candidates could be stored |
Idempotency (Submit Retries)
A submit retry is identified by the same provider + submissionId on the same site.
- (a) Happy path. First submit of
GC-2026-001→ batch stored, status 2 (processed). Response: SUCCESS. - (b) Retry after full processing. Second submit of
GC-2026-001→ rejected, HTTP 400, "Duplicate submission already processed: GC-2026-001". The original batch is untouched. A fully-processed batch is immutable viasubmit; useadd/update/deleteto change it. - (c) Retry after incomplete processing. If the existing
GC-2026-001batch is in any state other than fully processed (received, partial, failed), the old batch is marked superseded (soft-deleted) and the retry creates a fresh batch from the new payload. This is the intended recovery path after a partial failure: fix the offending records and re-submit the entire batch under the samesubmissionId.
🔄 [TEMP finalize before release] Send an identical provider string on every call for a given batch. The retry match on provider is exact and case/separator-sensitive (unlike the provider-active check), so varying its casing or separators between submit and a retry e.g. GlobalCampus vs. globalcampus will not be recognized as the same batch and will create a duplicate batch instead of the (b)/(c) behavior above.
Duplicate Candidates
- Within a single submit: if the same
providerCandidateIdappears twice, the first occurrence wins; later duplicates are skipped and audited. - On add: if the
providerCandidateIdalready exists as a live record in the batch, the add is skipped and audited (the existing record is not modified - useupdatefor that). - On update/delete: if no live record matches the
providerCandidateId, that entry is skipped and audited; the request still returns SUCCESS.
🔄 [TEMP finalize before release] Candidate LimitsTwo limits apply:
Per-request ceiling (hard reject). A single request whose reviewers array exceeds twice the configured maximum is rejected at the request level (HTTP 400; nothing stored). With the default maximum of 100, a request of more than 200 candidates is rejected. This guards against runaway payloads.
Per-document / per-provider cap (truncate-and-audit). Within that ceiling, candidates are accepted up to the provider's total live candidate count for the manuscript - counted across all of that provider's live batches, not per request. The maximum defaults to 100 and is clamped to a hard ceiling of 100 (it may be lowered per site, never raised above 100). When the provider already has live suggestions on the manuscript (a follow-up add, or a second submissionId), a growth allowance of +50 applies, so the per-document/provider total may reach max + 50 (never more than 150). Candidates beyond the cap are skipped and written to the audit trail; on submit the batch is marked partial (on add, over-cap candidates are audited but the batch status is not changed).
If ScholarOne cannot determine the current live count (a data-tier error), the request is rejected rather than risk exceeding the limit.
Delete Behavior and Modeling User Actions
Delete is a soft delete: the record disappears from all editor-facing views but is retained internally. A deleted providerCandidateId can be re-added later via add;🔄 [TEMP - finalize before release] this creates a new record with a new activity time (so it surfaces at the top of the editor's list - see Display Order).
If an editor has already actioned a candidate (e.g. sent a review invitation), that workflow is independent of this API; deleting the suggestion does not retract the invitation.
Automated vs. user-selected candidates: the API does not record why a candidate was sent (there is no addedBy field). Both kinds coexist freely in one batch and are distinguished only by providerCandidateId. If your system needs the distinction to be visible to editors or to itself later, either:
- Use separate batches (
submissionIds) for the automated run vs. user selections, or - Carry an origin marker in
providerData(e.g.[{"origin": "user-selected"}]), which editors can see in the candidate's detail view.
Ordering and Concurrency
Display Order
🔄 [TEMP - finalize before release] Candidates are ordered for editors newest first by the record's most recent activity - the time ScholarOne added or last updated it - with relevanceScore (descending) as the tiebreaker; records without a score sort last. Because an update re-stamps this time, editing any field on a candidate resurfaces it at the top of its provider section, just as a newly added candidate does. Records within a single submit are stamped individually as they are inserted, so they sort in insert order, with relevance as the tiebreaker only among records stamped at the same instant. Editors can also re-sort and filter the list in the interface (by name, relevance, and the provider-supplied metric columns).
Operation Ordering
Operations are applied in the order ScholarOne receives them. The timestamp field is informational and is not used to order or reconcile operations. There is no server-side conflict resolution for out-of-order delivery: for example, if a delete that was issued before a re-add arrives after it, the candidate ends up deleted.
Providers should therefore serialize operations per candidate - wait for the response to one operation before issuing the next operation that touches the same providerCandidateId. Independent candidates may be sent concurrently.
Quick Reference - What Fails Where
Updated 4 days ago

