Connected sources, their cursors, the sync batch contract, and what a revoke does immediately.
What a connection is
A connection is a durable record of a source you run: a file server, an S3, R2 or MinIO bucket. The mode is always local_agent, and that word carries the whole security posture — the agent runs in your environment and pushes outward; TAVONEL reaches into nothing. secretReference must be null, because a local agent uses its own workload credentials and we never hold them. configuration carries only non-secret selectors: a bucket, a prefix, a region, a root label.
GET/connectionsScope connections:read
Tenant-scoped durable source connections and their committed cursor state. Takes no paging parameters. The cursor is opaque: it is a sha256 over the collector's own position, and the only supported way to advance it is to send a batch whose `previousCursorSha256` matches the committed one.
curl -sS -X GET https://tavonel.com/api/v1/connections \
-H "Authorization: Bearer $TAVONEL_API_KEY"
The workspace's connections, with the committed cursor on each.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
403
API_SCOPE_REQUIRED — The key authenticated and does not carry the scope this operation requires. The request was refused rather than answered with less. PILOT_ACCESS_REQUIRED — The credential is valid and the workspace it names is not admitted to TAVONEL.
503
DEVELOPER_STORE_READ_FAILED — The developer store could not be read. DEVELOPER_STORE_NOT_CONFIGURED — The developer store is not configured. DEVELOPER_STORE_BINDING_INVALID — A stored row did not validate, so it was refused rather than returned partially.
POST/connectionsScope connections:write
Registers a local-agent file-server, S3, R2 or MinIO source. Credentials stay in the customer environment: `secretReference` must be null, because a local agent uses its own workload credentials and TAVONEL never holds them. The agent pushes outward; nothing here reaches into your network.
curl -sS -X POST https://tavonel.com/api/v1/connections \
-H "Authorization: Bearer $TAVONEL_API_KEY"
The durable connection record, with an empty cursor.
400
CONNECTION_INPUT_INVALID — The connection body did not match ConnectionInput. INVALID_JSON — The body was not parseable JSON.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
403
API_SCOPE_REQUIRED — The key authenticated and does not carry the scope this operation requires. The request was refused rather than answered with less. PILOT_ACCESS_REQUIRED — The credential is valid and the workspace it names is not admitted to TAVONEL.
413
REQUEST_TOO_LARGE — The JSON body exceeded the route's bound.
503
CONNECTION_CREATE_FAILED — The connection could not be recorded. DEVELOPER_STORE_NOT_CONFIGURED — The developer store is not configured.
The cursor, and what it is made of
Each connection carries one committed cursor, published as cursorSha256 and written sha256: followed by 64 hex characters. It is opaque: it is a digest over the collector's own position, not a timestamp, a page number or an offset you can construct. A connection that has never synced carries null.
The only way to move it is to send a batch whose previousCursorSha256 equals the committed value. That is an optimistic lock, and it is what stops two collectors from both advancing one connection: the second one's batch does not match, the whole batch is refused with 409 CONNECTION_BATCH_CONFLICT, and nothing is applied. Re-read the cursor and rebuild from it — never retry the same batch against a moved cursor.
The sync batch contract
Field
What it carries
batchId
A UUID you choose. Replaying the identical batchId is idempotent — delivery is at-least-once and this is the consumer that makes it exactly-once. The response says which happened: status is applied or replayed.
previousCursorSha256
The cursor as you last read it, or null for a connection that has never synced. The optimistic lock.
nextCursorSha256
Where the collector stands after this batch. Committed only if every event in the batch is accepted.
manifestSha256
A digest over the event set, so a truncated or reordered batch is refused rather than half-applied.
events
Up to 5,000 ConnectionEvent objects.
A ConnectionEvent is one observed change at the source. kind is added, changed or deleted. nativeId is the source's own identifier, stable across revisions — an object key, a path, a file id. revision is the source's own version marker: an ETag, an mtime, a version id. Every field is required and several are explicitly nullable, which is the point: a collector that cannot compute a digest sends contentSha256: null rather than omitting the field, so "not known" and "not sent" are different states the server can tell apart.
documentId and sourceIdempotencyKey travel together. Where the source type is qualified, the agent first requests an upload capability with x-tavonel-source-idempotency-key set to a sha256 over the source event, which makes the document id deterministic — a retried collection converges on one document instead of two. The sync batch then names that same pair, and the server revalidates it rather than trusting it.
POST/connections/{id}/syncScope connections:sync
Applies one batch of source events and advances the cursor. `previousCursorSha256` must match the committed cursor, so two collectors cannot both advance it; a mismatch is a 409 and nothing is applied. Replaying an identical `batchId` is idempotent — delivery is at-least-once and this is the consumer that makes it exactly-once.
curl -sS -X POST https://tavonel.com/api/v1/connections/{id}/sync \
-H "Authorization: Bearer $TAVONEL_API_KEY"
The cursor transition, applied or idempotently replayed. status: replayed means this exact batch had already been applied and nothing changed.
400
CONNECTION_BATCH_INVALID — The sync batch did not match ConnectionBatch. CONNECTION_ID_INVALID — The connection id was not a UUID. SOURCE_IDENTITY_INVALID — The event's nativeId/revision pair did not validate. SOURCE_DIGEST_REQUIRED — An event carried bytes with no contentSha256. SOURCE_METADATA_INVALID — Event metadata did not validate. INVALID_JSON — The body was not parseable JSON.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
404
CONNECTION_NOT_FOUND — No such connection in this workspace.
409
CONNECTION_BATCH_CONFLICT — The batch's previousCursorSha256 does not match the committed cursor — another sync moved it. CONNECTION_CURSOR_CONFLICT — Two batches tried to advance the same cursor. SOURCE_CURSOR_STALE — The cursor the batch carries is behind the committed one. SOURCE_DIGEST_CONFLICT — Two events declare different digests for the same revision. SOURCE_DIGEST_MISMATCH — The bytes do not hash to the digest the event declared. SOURCE_VERSION_DIGEST_CONFLICT — A version is already recorded with a different digest.
413
REQUEST_TOO_LARGE — The JSON body exceeded the route's bound. SOURCE_CURSOR_TOO_LARGE — The cursor value exceeded its bound.
423
CONNECTION_NOT_SYNCABLE — The connection is revoked or in a state that does not accept a batch. SOURCE_LIFECYCLE_REVIEW_REQUIRED — A lifecycle transition on this source needs a person.
503
CONNECTION_BATCH_FAILED — The batch could not be applied.
Revoking, and what survives it
A revoke takes effect on the next request rather than waiting for a background reindex. Immutable outputs already compiled are retained: a revoke stops future reads, it does not rewrite history, and a World compiled from that source keeps citing the source version it actually read. A revoke that the store could not record answers 503 rather than 204 — it is reported only when it is written.
DELETE/connections/{id}Scope connections:write
Revokes the connection. Access removal takes effect on the next request rather than waiting for a background reindex. Immutable outputs already compiled are retained: a revoke stops future reads, it does not rewrite history.
Connection revoked; immutable outputs retained. No body.
400
CONNECTION_ID_INVALID — The connection id was not a UUID.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
404
CONNECTION_NOT_FOUND — No such connection in this workspace.
503
CONNECTION_REVOKE_FAILED — The revoke could not be recorded, so it is not reported as done. Immutable outputs are retained by design when a revoke does succeed.
OAuth connectors
Where a provider is configured, a connection can be created through OAuth instead of a local agent. The authorization is single-use and PKCE, and it fails closed: unless both the provider client and the managed secret broker are configured, no authorization is started, because an authorization that cannot store a refresh secret is an authorization that ends in a broken connection. A revoke deletes the stored refresh secret and is reported as done only when the secret is actually gone.
GET/oauth-connectors
Lists configured provider readiness and tenant OAuth connections. Provider credentials are never returned. A provider whose client is not configured reports `configured: false` rather than being hidden.
# Browser session only. A developer API key is refused on this route.
curl -sS -X GET https://tavonel.com/api/v1/oauth-connectors \
-H "Authorization: Bearer $TAVONEL_SESSION_JWT"
# Browser session only. A developer API key is refused on this route.
import os
import requests
response = requests.request(
"GET",
"https://tavonel.com/api/v1/oauth-connectors",
headers={"Authorization": "Bearer " + os.environ["TAVONEL_SESSION_JWT"]},
timeout=30,
)
response.raise_for_status()
print(response.json())
// Browser session only. A developer API key is refused on this route.
const response = await fetch("https://tavonel.com/api/v1/oauth-connectors", {
method: "GET",
headers: { authorization: `Bearer ${process.env.TAVONEL_SESSION_JWT}` },
});
if (!response.ok) throw new Error(`${response.status} ${(await response.json()).code}`);
console.log(await response.json());
Status
Response
200
Provider readiness and the workspace's connections.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
503
OAUTH_STORE_UNAVAILABLE — The OAuth store could not be reached. OAUTH_STORE_NOT_CONFIGURED — The OAuth store is not configured.
POST/oauth-connectors/authorize
Creates a single-use PKCE authorization. Fails closed unless the provider client and the managed secret broker are both configured — an authorization that cannot store a refresh secret is not started.
# Browser session only. A developer API key is refused on this route.
curl -sS -X POST https://tavonel.com/api/v1/oauth-connectors/authorize \
-H "Authorization: Bearer $TAVONEL_SESSION_JWT"
# Browser session only. A developer API key is refused on this route.
import os
import requests
response = requests.request(
"POST",
"https://tavonel.com/api/v1/oauth-connectors/authorize",
headers={"Authorization": "Bearer " + os.environ["TAVONEL_SESSION_JWT"]},
timeout=30,
)
response.raise_for_status()
print(response.json())
// Browser session only. A developer API key is refused on this route.
const response = await fetch("https://tavonel.com/api/v1/oauth-connectors/authorize", {
method: "POST",
headers: { authorization: `Bearer ${process.env.TAVONEL_SESSION_JWT}` },
});
if (!response.ok) throw new Error(`${response.status} ${(await response.json()).code}`);
console.log(await response.json());
Status
Response
200
A short-lived provider authorization URL. Single-use: a second visit to it is refused.
400
OAUTH_CONNECTOR_INPUT_INVALID — The authorization body did not match OAuthConnectorAuthorizationInput. INVALID_JSON — The body was not parseable JSON.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
413
REQUEST_TOO_LARGE — The JSON body exceeded the route's bound.
503
OAUTH_PROVIDER_NOT_CONFIGURED — The provider's client is not configured. Fails closed rather than starting an authorization that cannot complete. OAUTH_SECRET_BROKER_NOT_CONFIGURED — The managed secret broker is not configured, so no refresh secret could be stored — or, on a revoke, deleted. OAUTH_AUTHORIZATION_START_FAILED — The single-use PKCE authorization could not be created.
DELETE/oauth-connectors/connections/{id}
Deletes the stored refresh secret and revokes the connection. Reported as done only when the secret is actually gone — a revoke that cannot be proven answers 503 rather than 204.
# Browser session only. A developer API key is refused on this route.
curl -sS -X DELETE https://tavonel.com/api/v1/oauth-connectors/connections/{id} \
-H "Authorization: Bearer $TAVONEL_SESSION_JWT"
# Browser session only. A developer API key is refused on this route.
import os
import requests
response = requests.request(
"DELETE",
"https://tavonel.com/api/v1/oauth-connectors/connections/{id}",
headers={"Authorization": "Bearer " + os.environ["TAVONEL_SESSION_JWT"]},
timeout=30,
)
response.raise_for_status()
print(response.json())
// Browser session only. A developer API key is refused on this route.
const response = await fetch("https://tavonel.com/api/v1/oauth-connectors/connections/{id}", {
method: "DELETE",
headers: { authorization: `Bearer ${process.env.TAVONEL_SESSION_JWT}` },
});
if (!response.ok) throw new Error(`${response.status} ${(await response.json()).code}`);
console.log(await response.json());
Status
Response
204
Refresh secret deleted and connection revoked. No body.
400
OAUTH_CONNECTION_ID_INVALID — The OAuth connection id was not a UUID.
401
AUTH_REQUIRED — No credential was presented, or the bearer token is not a key TAVONEL issued. This is what an unauthenticated request to any scoped route returns.
404
OAUTH_CONNECTION_NOT_FOUND — No such OAuth connection in this workspace.
503
OAUTH_SECRET_REVOCATION_FAILED — The refresh secret could not be deleted, so the revoke was not reported as done. OAUTH_SECRET_BROKER_NOT_CONFIGURED — The managed secret broker is not configured, so no refresh secret could be stored — or, on a revoke, deleted.
Availability by provider
NoteConnector availability differs by provider and by workspace, and listOAuthConnectors reports configured: false for a provider whose client TAVONEL does not hold rather than hiding it. The Integrations page states which are live; this page does not restate it, because two pages saying different things about the same connector is how that goes wrong.
API version 2026-09-02.1 · reviewed 11 September 2026