Skip to main content

PUT sync mode

PUT creates entities that don't exist yet and updates the ones that do. Unlike DIFF, it never deletes: anything that was in the scope last time and is missing from this upload is left exactly as it was.

This is the mode for sources that can only show you part of the picture. If your API exposes a rolling window — the last seven days of records, say — DIFF would delete everything older on every run, and PATCH would refuse to create the records that are new. PUT is the middle ground: upsert what you have, leave the rest alone.

Enablement is per-account

PUT is enabled per account by JupiterOne. Requests from accounts that aren't enabled are rejected with PUT sync mode is not enabled for this account. To have it turned on, contact JupiterOne support with your account ID and a short description of the data source. See Requesting access.

When to use PUT

  • Your source can't give you the full dataset on every run, so DIFF's delete sweep would remove valid data.
  • New records appear over time and need to be created, which rules out PATCH.
  • You are the source of truth for the entities you do send, and you want the upload to win on those.
  • You accept that records which age out of your source stay in the graph until something else removes them.
  • You need to upload relationships as well as entities. PUT accepts both, including mapped relationships.

If your source can produce the full dataset, prefer DIFF — automatic cleanup of stale data is worth having.

Example use cases

A source API with a rolling window

Your GRC platform exposes findings through an API that only returns the last seven days of activity. You poll it nightly and push the results into JupiterOne.

  • Scope: the custom integration instance
  • Mode: PUT
  • Why PUT: DIFF would delete every finding older than a week on the first run, because they aren't in the upload. PATCH would keep existing findings up to date but silently skip the ones created since yesterday. PUT creates the new findings and refreshes the ones it can still see, and leaves the older history in place.
POST /persister/synchronization/jobs
{
"source": "integration-external",
"integrationInstanceId": "d246255c-1530-4fd7-9e99-6c2323402483",
"syncMode": "PUT"
}
POST /persister/synchronization/jobs/{jobId}/upload
{
"entities": [
{
"_key": "grc-finding:2718471",
"_type": "grc_finding",
"_class": "Finding",
"status": "open",
"severity": "high",
"identifiedOn": "2026-04-17T13:45:53Z"
}
]
}

An append-only event feed

An internal service emits audit events to a queue. A worker batches them and pushes them into JupiterOne every few minutes. Events are never revised and never deleted at the source.

  • Mode: PUT
  • Why PUT: Each batch is genuinely new data. DIFF would treat every batch as the complete history and delete everything from the previous batch.

A slow-moving dataset you only partially refresh

You mirror a large asset inventory, but a full export takes hours, so you refresh it in slices — one business unit per run.

  • Mode: PUT
  • Why PUT: Each run is a valid partial upload. Under DIFF, syncing one business unit would delete the others.

Requesting access

PUT is gated per account while it rolls out. To request it, contact JupiterOne support with:

  • the account ID you want it enabled for (each account is enabled separately — a sandbox or POV account and a production account are two requests);
  • the data source you're ingesting and why a rolling window or partial upload means DIFF isn't suitable;
  • whether you'll be uploading relationships as well as entities.

Enablement is a configuration change on JupiterOne's side, so allow for a normal support turnaround rather than expecting it to be self-serve. Until your account is enabled, starting a PUT job returns:

PUT sync mode is not enabled for this account

Nothing else about the request needs to change once it's enabled — the same payload starts working.

Required entity fields

Every entity in a PUT upload needs:

  • _key — unique within the scope. This is what PUT matches on to decide between create and update.
  • _type — your entity type, in snake_case.
  • _class — JupiterOne class (string or array of strings, max 5 items).

These are the same three fields DIFF requires, and for the same reason: PUT creates entities, so it needs enough to build one.

note

PUT identifies entities by _key only. Unlike PATCH, it does not accept _id as an alternative identifier — an upload containing _id is rejected.

Required relationship fields

Standard relationships need _key, _type, _class, _fromEntityKey, and _toEntityKey.

Mapped relationships — for connecting to entities you don't own in this scope — need _mapping instead of _fromEntityKey/_toEntityKey. PUT writes mapped relationships, the same as DIFF does. See the API reference for the mapped-relationship shape.

As with entities, relationships are upserted and never deleted. A relationship that stops appearing in your uploads stays in the graph.

How updates are applied

When PUT updates an entity that already exists, it writes the properties present in your payload and leaves every other property alone. Properties you stop sending keep their previous values — PUT has no way to tell "this property is gone" from "this property wasn't in this batch."

Values pinned with OVERRIDE are respected: if a property on the target entity is held by an override, PUT will not overwrite it.

Properties cannot be cleared with PUT

Sending a property as null does not clear it. Null values are stripped from PUT uploads before they reach the graph, so the property keeps whatever value it had. (This differs from PATCH, where null does clear a property.)

To remove a property from a PUT-maintained entity, use the entity mutation API, or run a DIFF over the scope with the property omitted.

Example: full PUT sync job

POST /persister/synchronization/jobs
{
"source": "integration-external",
"integrationInstanceId": "d246255c-1530-4fd7-9e99-6c2323402483",
"syncMode": "PUT"
}

Response:

{
"job": {
"id": "f445397d-8491-4a12-806a-04792839abe3",
"status": "AWAITING_UPLOADS",
"syncMode": "PUT",
"numEntitiesUploaded": 0
}
}

Upload entities and relationships:

POST /persister/synchronization/jobs/{jobId}/upload
{
"entities": [
{
"_key": "grc-finding:2718471",
"_type": "grc_finding",
"_class": "Finding",
"status": "open",
"severity": "high"
},
{
"_key": "grc-supplier:8891",
"_type": "grc_supplier",
"_class": "Vendor",
"displayName": "Northwind Logistics"
}
],
"relationships": [
{
"_key": "grc-finding:2718471|affects|grc-supplier:8891",
"_type": "grc_finding_affects_supplier",
"_class": "AFFECTS",
"_fromEntityKey": "grc-finding:2718471",
"_toEntityKey": "grc-supplier:8891"
}
]
}

Finalize:

POST /persister/synchronization/jobs/{jobId}/finalize

After finalize, those two entities and the relationship between them exist and are current. Everything else already in the scope is untouched — nothing was deleted.

Nothing is ever removed

This is the point of PUT, and it's worth stating plainly: a PUT job has no delete step. Records that disappear from your source stay in the graph indefinitely.

That's the correct behavior when your source genuinely can't tell you what was removed. It is the wrong behavior if you want stale data cleaned up — in which case use DIFF.

If you need to remove entities from a PUT-maintained scope, you have two options:

  • Run a one-off DIFF job over the same scope with the complete dataset, which reconciles it in a single pass.
  • Delete the entities individually through the entity mutation API.

Known limitation: property search on updated entities

When PUT creates an entity, the entity is fully indexed and behaves like any other.

When PUT updates an existing entity, the search index and change-detection data for that entity are not recomputed from the merged result — they stay as they were when the entity was created. In practice this means a property-value search may not match a value that was added by a later PUT update, even though the property is present on the entity and returned by queries that select it directly.

Entities that a managed integration or a DIFF job also writes are unaffected: those writes refresh the index normally.

If property-value search across the full history of an entity matters for your use case, plan on a periodic DIFF over the scope, or filter on properties in your queries rather than relying on free-text search. This is a current limitation and is expected to change.

Operational notes

  • Partial uploads are safe. That's the entire premise — send what you have.
  • Stable _keys matter more than usual. With no delete sweep, a change in your _key strategy doesn't churn the data, it duplicates it: the old entities stay and the new ones are created alongside them.
  • Re-running the same upload is a no-op. PUT is idempotent.
  • Chunk across uploads, finalize once. A single sync job accepts many upload calls before finalize.
  • PUT does not protect properties from managed integrations. If a managed integration owns the entity, its next sync rewrites the entity and any PUT-set property it doesn't itself assert is lost. Use OVERRIDE for values that must survive integration syncs.

PUT vs DIFF vs PATCH

Creates entitiesUpdates entitiesDeletes what's missingAccepts relationships
DIFFyesyesyesyes
PATCHnoyesnono
PUTyesyesnoyes

The short version: pick DIFF when your upload is the whole truth, PATCH when you're only enriching entities that already exist, and PUT when you have partial data that includes records nobody has seen before.

Common errors

ErrorCauseFix
PUT sync mode is not enabled for this accountThe account isn't on the PUT allowlist.Contact support to request enablement. See Requesting access.
Validation error naming _idAn entity was uploaded with _id.PUT identifies entities by _key. Remove _id.
/entities/0/_key is requiredMissing _key.Add _keyPUT requires it to match create against update.
Entities linger after they leave your sourceWorking as designed; PUT never deletes.Run a DIFF over the scope, or delete via the entity mutation API.
A property added by a later update isn't found by property searchThe search index isn't recomputed on PUT update.See Known limitation.
A property sent as null still has its old valueNulls are stripped from PUT uploads.Clear it via the entity mutation API or a DIFF over the scope.

See the API reference for the complete error table.