").deidentifyText(deidentifyTextRequest);
-
- // Step 5: Print the response
- System.out.println("Deidentify text Response: " + deidentifyTextResponse);
- }
-}
-
-```
-
-## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/DeidentifyTextExample.java) of deidentify text:
-```java
-import java.util.ArrayList;
-import java.util.List;
-
-import com.skyflow.enums.DetectEntities;
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.detect.DateTransformation;
-import com.skyflow.vault.detect.DeidentifyTextRequest;
-import com.skyflow.vault.detect.DeidentifyTextResponse;
-import com.skyflow.vault.detect.TokenFormat;
-import com.skyflow.vault.detect.Transformations;
-
-/**
- * Skyflow Deidentify Text Example
- *
- * This example demonstrates how to use the Skyflow SDK to deidentify text data
- * across multiple vaults. It includes:
- * 1. Setting up credentials and vault configurations.
- * 2. Creating a Skyflow client with multiple vaults.
- * 3. Performing deidentify of text with various options.
- * 4. Handling responses and errors.
- */
-
-public class DeidentifyTextExample {
- public static void main(String[] args) throws SkyflowException {
-
- // Step 1: Initialize the Skyflow client by configuring the credentials & vault config.
-
- // Step 2: Configuring the different options for deidentify
-
- // Replace with the entity you want to detect
- List detectEntitiesList = new ArrayList<>();
- detectEntitiesList.add(DetectEntities.SSN);
- detectEntitiesList.add(DetectEntities.CREDIT_CARD);
-
- // Replace with the entity you want to detect with vault token
- List vaultTokenList = new ArrayList<>();
- vaultTokenList.add(DetectEntities.SSN);
- vaultTokenList.add(DetectEntities.CREDIT_CARD);
-
- // Configure Token Format
- TokenFormat tokenFormat = TokenFormat.builder()
- .vaultToken(vaultTokenList)
- .build();
-
- // Configure Transformation for deidentified entities
- List detectEntitiesTransformationList = new ArrayList<>();
- detectEntitiesTransformationList.add(DetectEntities.DOB); // Replace with the entity you want to transform
-
- DateTransformation dateTransformation = new DateTransformation(20, 5, detectEntitiesTransformationList);
- Transformations transformations = new Transformations(dateTransformation);
-
- // Step 3: invoking Deidentify text on the vault
- try {
- // Create a deidentify text request for the vault
- DeidentifyTextRequest deidentifyTextRequest = DeidentifyTextRequest.builder()
- .text("My SSN is 123-45-6789 and my card is 4111 1111 1111 1111.") // Replace with your deidentify text
- .entities(detectEntitiesList)
- .tokenFormat(tokenFormat)
- .transformations(transformations)
- .build();
- // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id
- DeidentifyTextResponse deidentifyTextResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").deidentifyText(deidentifyTextRequest);
-
- System.out.println("Deidentify text Response: " + deidentifyTextResponse);
- } catch (SkyflowException e) {
- System.err.println("Error occurred during deidentify: ");
- e.printStackTrace(); // Print the exception for debugging purposes
- }
- }
-}
-```
-
-Sample Response:
-```json
-{
- "processedText": "My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].",
- "entities": [
- {
- "token": "SSN_IWdexZe",
- "value": "123-45-6789",
- "textIndex": {
- "start": 10,
- "end": 21
- },
- "processedIndex": {
- "start": 10,
- "end": 23
- },
- "entity": "SSN",
- "scores": {
- "SSN": 0.9384
- }
- },
- {
- "token": "CREDIT_CARD_rUzMjdQ",
- "value": "4111 1111 1111 1111",
- "textIndex": {
- "start": 37,
- "end": 56
- },
- "processedIndex": {
- "start": 39,
- "end": 60
- },
- "entity": "CREDIT_CARD",
- "scores": {
- "CREDIT_CARD": 0.9051
- }
- }
- ],
- "wordCount": 9,
- "charCount": 57
-}
-```
-
-## Reidentify Text
-To reidentify text, use the `reidentifyText` method. [`ReidentifyTextRequest`](docs/api_reference.md#reidentifytextrequest) accepts the redacted/deidentified text and optional entity lists controlling which entities to reveal, mask, or keep redacted. Returns a [`ReidentifyTextResponse`](docs/api_reference.md#reidentifytextresponse).
-
-### Construct an reidentify text request
-
-```java
-import com.skyflow.enums.DetectEntities;
-import com.skyflow.vault.detect.ReidentifyTextRequest;
-import com.skyflow.vault.detect.ReidentifyTextResponse;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This example demonstrates how to build a reidentify text request.
- */
-public class ReidentifyTextSchema {
- public static void main(String[] args) {
- // Step 1: Initialize the Skyflow client by configuring the credentials & vault config.
-
- // Step 2: Configuring the different options for reidentify
- List maskedEntity = new ArrayList<>();
- maskedEntity.add(DetectEntities.CREDIT_CARD); // Replace with the entity you want to mask
-
- List plainTextEntity = new ArrayList<>();
- plainTextEntity.add(DetectEntities.SSN); // Replace with the entity you want to keep in plain text
-
- // List redactedEntity = new ArrayList<>();
- // redactedEntity.add(DetectEntities.SSN); // Replace with the entity you want to redact
-
-
- // Step 3: Create a reidentify text request with the configured entities
- ReidentifyTextRequest reidentifyTextRequest = ReidentifyTextRequest.builder()
- .text("My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].") // Replace with your deidentify text
- .maskedEntities(maskedEntity)
-// .redactedEntities(redactedEntity)
- .plainTextEntities(plainTextEntity)
- .build();
-
- // Step 4: Invoke reidentify text on the vault
- ReidentifyTextResponse reidentifyTextResponse = skyflowClient.detect("").reidentifyText(reidentifyTextRequest);
- System.out.println("Reidentify text Response: " + reidentifyTextResponse);
- }
-}
-```
-
-## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/ReidentifyTextExample.java) of Reidentify text
-
-```java
-import com.skyflow.enums.DetectEntities;
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.detect.ReidentifyTextRequest;
-import com.skyflow.vault.detect.ReidentifyTextResponse;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Skyflow Reidentify Text Example
- *
- * This example demonstrates how to use the Skyflow SDK to reidentify text data
- * across multiple vaults. It includes:
- * 1. Setting up credentials and vault configurations.
- * 2. Creating a Skyflow client with multiple vaults.
- * 3. Performing reidentify of text with various options.
- * 4. Handling responses and errors.
- */
-
-public class ReidentifyTextExample {
- public static void main(String[] args) throws SkyflowException {
- // Step 1: Initialize the Skyflow client by configuring the credentials & vault config.
-
- // Step 2: Configuring the different options for reidentify
- List maskedEntity = new ArrayList<>();
- maskedEntity.add(DetectEntities.CREDIT_CARD); // Replace with the entity you want to mask
-
- List plainTextEntity = new ArrayList<>();
- plainTextEntity.add(DetectEntities.SSN); // Replace with the entity you want to keep in plain text
-
- try {
- // Step 3: Create a reidentify text request with the configured options
- ReidentifyTextRequest reidentifyTextRequest = ReidentifyTextRequest.builder()
- .text("My SSN is [SSN_IWdexZe] and my card is [CREDIT_CARD_rUzMjdQ].") // Replace with your deidentify text
- .maskedEntities(maskedEntity)
- .plainTextEntities(plainTextEntity)
- .build();
-
- // Step 4: Invoke Reidentify text on the vault
- // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id
- ReidentifyTextResponse reidentifyTextResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").reidentifyText(reidentifyTextRequest);
-
- // Handle the response from the reidentify text request
- System.out.println("Reidentify text Response: " + reidentifyTextResponse);
- } catch (SkyflowException e) {
- System.err.println("Error occurred during reidentify : ");
- e.printStackTrace();
- }
- }
-}
-```
-
-Sample Response:
-
-```json
-{
- "processedText":"My SSN is 123-45-6789 and my card is XXXXX1111."
-}
-```
-
-## Deidentify file
-To deidentify files, use the `deidentifyFile` method. [`DeidentifyFileRequest`](docs/api_reference.md#deidentifyfilerequest) accepts a [`FileInput`](docs/api_reference.md#fileinput) and optional parameters controlling entity detection, masking, output format, and async wait time. Supports images, PDFs, audio, documents, spreadsheets, and presentations. Returns a [`DeidentifyFileResponse`](docs/api_reference.md#deidentifyfileresponse).
-
-### AudioBleep
-
-[`AudioBleep`](docs/api_reference.md#audiobleep) controls how detected sensitive audio segments are replaced with a bleep tone. Used in `DeidentifyFileRequest.builder().bleep(audioBleep)` for audio files.
-
-```java
-import com.skyflow.vault.detect.AudioBleep;
-
-AudioBleep audioBleep = AudioBleep.builder()
- .frequency(1000D) // bleep tone frequency in Hz
- .gain(0.5D) // bleep tone gain (volume level)
- .startPadding(0.2D) // silence padding before the bleep (seconds)
- .stopPadding(0.2D) // silence padding after the bleep (seconds)
- .build();
-```
-
-### Construct an deidentify file request
-
-```java
-import com.skyflow.config.Credentials;
-import com.skyflow.config.VaultConfig;
-import com.skyflow.enums.Env;
-import com.skyflow.enums.LogLevel;
-import com.skyflow.enums.MaskingMethod;
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.detect.DeidentifyFileRequest;
-import com.skyflow.vault.detect.DeidentifyFileResponse;
-
-import java.io.File;
-
-/**
- * This example demonstrates how to build a deidentify file request.
- */
-
-public class DeidentifyFileSchema {
-
- public static void main(String[] args) {
- // Step 1: Initialize the Skyflow client by configuring the credentials & vault config.
-
- // Step 2: Create a deidentify file request with all options
-
- // Create file object
- File file = new File(""); // Replace with the path to the file you want to deidentify
-
- // Create file input using the file object
- FileInput fileInput = FileInput.builder()
- .file(file)
- // .filePath("") // Alternatively, you can use .filePath()
- .build();
-
- // Output configuration
- String outputDirectory = ""; // Replace with the desired output directory to save the deidentified file
-
- // Entities to detect
- // List detectEntities = new ArrayList<>();
- // detectEntities.add(DetectEntities.IP_ADDRESS); // Replace with the entities you want to detect
-
- // Image-specific options
- // Boolean outputProcessedImage = true; // Include processed image in output
- // Boolean outputOcrText = true; // Include OCR text in output
- MaskingMethod maskingMethod = MaskingMethod.BLACKBOX; // Masking method for images
-
- // PDF-specific options
- // Integer pixelDensity = 15; // Pixel density for PDF processing
- // Integer maxResolution = 2000; // Max resolution for PDF
-
- // Audio-specific options
- // Boolean outputProcessedAudio = true; // Include processed audio
- // DetectOutputTranscriptions outputTanscription = DetectOutputTranscriptions.PLAINTEXT_TRANSCRIPTION; // Transcription type
-
- // Audio bleep configuration
- // AudioBleep audioBleep = AudioBleep.builder()
- // .frequency(5D) // Pitch in Hz
- // .startPadding(7D) // Padding at start (seconds)
- // .stopPadding(8D) // Padding at end (seconds)
- // .build();
-
- Integer waitTime = 20; // Max wait time for response (max 64 seconds)
-
- DeidentifyFileRequest deidentifyFileRequest = DeidentifyFileRequest.builder()
- .file(fileInput)
- .waitTime(waitTime)
- .entities(detectEntities)
- .outputDirectory(outputDirectory)
- .maskingMethod(maskingMethod)
- // .outputProcessedImage(outputProcessedImage)
- // .outputOcrText(outputOcrText)
- // .pixelDensity(pixelDensity)
- // .maxResolution(maxResolution)
- // .outputProcessedAudio(outputProcessedAudio)
- // .outputTranscription(outputTanscription)
- // .bleep(audioBleep)
- .build();
-
-
- DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("").deidentifyFile(deidentifyFileRequest);
- System.out.println("Deidentify file response: " + deidentifyFileResponse.toString());
- }
-}
-```
-
-## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/DeidentifyFileExample.java) of Deidentify file
-
-```java
-import java.io.File;
-
-import com.skyflow.enums.MaskingMethod;
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.detect.DeidentifyFileRequest;
-import com.skyflow.vault.detect.DeidentifyFileResponse;
-
-/**
- * Skyflow Deidentify File Example
- *
- * This example demonstrates how to use the Skyflow SDK to deidentify file
- * It has all available options for deidentifying files.
- * Supported file types: images (jpg, png, etc.), pdf, audio (mp3, wav), documents, spreadsheets, presentations, structured text.
- * It includes:
- * 1. Configure credentials
- * 2. Set up vault configuration
- * 3. Create a deidentify file request with all options
- * 4. Call deidentifyFile to deidentify file.
- * 5. Handle response and errors
- */
-public class DeidentifyFileExample {
-
- public static void main(String[] args) throws SkyflowException {
- // Step 1: Initialize the Skyflow client by configuring the credentials & vault config.
- try {
- // Step 2: Create a deidentify file request with all options
-
-
- // Create file object
- File file = new File("sensitive-folder/personal-info.txt"); // Replace with the path to the file you want to deidentify
-
- // Create file input using the file object
- FileInput fileInput = FileInput.builder()
- .file(file)
- // .filePath("") // Alternatively, you can use .filePath()
- .build();
-
- // Output configuration
- String outputDirectory = "deidentified-file/"; // Replace with the desired output directory to save the deidentified file
-
- // Entities to detect
- // List detectEntities = new ArrayList<>();
- // detectEntities.add(DetectEntities.IP_ADDRESS); // Replace with the entities you want to detect
-
- // Image-specific options
- // Boolean outputProcessedImage = true; // Include processed image in output
- // Boolean outputOcrText = true; // Include OCR text in output
- MaskingMethod maskingMethod = MaskingMethod.BLACKBOX; // Masking method for images
-
- Integer waitTime = 20; // Max wait time for response (max 64 seconds)
-
- DeidentifyFileRequest deidentifyFileRequest = DeidentifyFileRequest.builder()
- .file(fileInput)
- .waitTime(waitTime)
- .outputDirectory(outputDirectory)
- .maskingMethod(maskingMethod)
- .build();
-
- // Step 3: Invoking deidentifyFile
- // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id
- DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").deidentifyFile(deidentifyFileRequest);
- System.out.println("Deidentify file response: " + deidentifyFileResponse.toString());
- } catch (SkyflowException e) {
- System.err.println("Error occurred during deidentify file: ");
- e.printStackTrace();
- }
- }
-}
-
-```
-
-Sample response:
-
-```json
-{
- "file": {
- "name": "deidentified.txt",
- "size": 33,
- "type": "",
- "lastModified": 1751355183039
- },
- "fileBase64": "bXkgY2FyZCBudW1iZXIgaXMgW0NSRURJVF",
- "type": "redacted_file",
- "extension": "txt",
- "wordCount": 11,
- "charCount": 61,
- "sizeInKb": 0,
- "entities": [
- {
- "file": "bmFtZTogW05BTUVfMV0gCm==",
- "type": "entities",
- "extension": "json"
- }
- ],
- "runId": "undefined",
- "status": "success"
-}
-
-```
-
-**Supported file types:**
-- Documents: `doc`, `docx`, `pdf`
-- PDFs: `pdf`
-- Images: `bmp`, `jpeg`, `jpg`, `png`, `tif`, `tiff`
-- Structured text: `json`, `xml`
-- Spreadsheets: `csv`, `xls`, `xlsx`
-- Presentations: `ppt`, `pptx`
-- Audio: `mp3`, `wav`
-
-**Note:**
-- Transformations cannot be applied to Documents, Images, or PDFs file formats.
-
-- The `waitTime` option must be ≤ 64 seconds; otherwise, an error is thrown.
-
-- If the API takes more than 64 seconds to process the file, it will return only the run ID in the response.
-
-Sample response (when the API takes more than 64 seconds):
-```json
-{
- "file": null,
- "fileBase64": null,
- "type": null,
- "extension": null,
- "wordCount": null,
- "charCount": null,
- "sizeInKb": null,
- "durationInSeconds": null,
- "pageCount": null,
- "slideCount": null,
- "entities": null,
- "runId": "1273a8c6-c498-4293-a9d6-389864cd3a44",
- "status": "IN_PROGRESS",
- "errors": null
-}
-```
-
-## Get run:
-To retrieve the results of a previously started file deidentification operation, use the `getDetectRun` method. [`GetDetectRunRequest`](docs/api_reference.md#getdetectrunrequest) accepts the `runId` returned from a prior `deidentifyFile` call. Returns a [`DeidentifyFileResponse`](docs/api_reference.md#deidentifyfileresponse).
-
-### Construct an get run request
-
-```java
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.detect.DeidentifyFileResponse;
-import com.skyflow.vault.detect.GetDetectRunRequest;
-
-/**
- * Skyflow Get Detect Run Example
- */
-
-public class GetDetectRunSchema {
-
- public static void main(String[] args) {
- try {
- // Step 1: Initialize the Skyflow client by configuring the credentials & vault config.
-
- // Step 2: Create a get detect run request
- GetDetectRunRequest getDetectRunRequest = GetDetectRunRequest.builder()
- .runId("") // Replace with the runId from deidentifyFile call
- .build();
-
- // Step 3: Call getDetectRun to poll for file processing results
- // Replace with your actual vault ID
- DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("").getDetectRun(getDetectRunRequest);
- System.out.println("Get Detect Run Response: " + deidentifyFileResponse);
- } catch (SkyflowException e) {
- System.err.println("Error occurred during get detect run: ");
- e.printStackTrace();
- }
- }
-}
-
-```
-
-## An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/detect/GetDetectRunExample.java) of get run
-```java
-import com.skyflow.config.Credentials;
-import com.skyflow.config.VaultConfig;
-import com.skyflow.enums.Env;
-import com.skyflow.enums.LogLevel;
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.detect.DeidentifyFileResponse;
-import com.skyflow.vault.detect.GetDetectRunRequest;
-
-/**
- * Skyflow Get Detect Run Example
- *
- * This example demonstrates how to:
- * 1. Configure credentials
- * 2. Set up vault configuration
- * 3. Create a get detect run request
- * 4. Call getDetectRun to poll for file processing results
- * 5. Handle response and errors
- */
-public class GetDetectRunExample {
- public static void main(String[] args) throws SkyflowException {
- // Step 1: Initialize the Skyflow client by configuring the credentials & vault config.
- try {
-
- // Step 2: Create a get detect run request
- GetDetectRunRequest getDetectRunRequest = GetDetectRunRequest.builder()
- .runId("e0038196-4a20-422b-bad7-e0477117f9bb") // Replace with the runId from deidentifyFile call
- .build();
-
- // Step 3: Call getDetectRun to poll for file processing results
- // Replace `9f27764a10f7946fe56b3258e117` with the actual vault id
- DeidentifyFileResponse deidentifyFileResponse = skyflowClient.detect("9f27764a10f7946fe56b3258e117").getDetectRun(getDetectRunRequest);
- System.out.println("Get Detect Run Response: " + deidentifyFileResponse);
- } catch (SkyflowException e) {
- System.err.println("Error occurred during get detect run: ");
- e.printStackTrace();
- }
- }
-}
-```
-
-Sample Response:
-
-```json
-{
- "file": "bmFtZTogW05BTET0JfMV0K",
- "type": "redacted_file",
- "extension": "txt",
- "wordCount": 11,
- "charCount": 61,
- "sizeInKb": 0.0,
- "entities": [
- {
- "file": "gW05BTUVfMV0gCmNhcmQ0K",
- "type": "entities",
- "extension": "json"
- }
- ],
- "runId": "e0038196-4a20-422b-bad7-e0477117f9bb",
- "status": "success"
-}
-
-```
-
-## Detect response types
-
-The Detect API returns structured objects for detected entities. See the API Reference for full attribute lists: [`EntityInfo`](docs/api_reference.md#entityinfo), [`TextIndex`](docs/api_reference.md#textindex), [`FileEntityInfo`](docs/api_reference.md#fileentityinfo), [`FileInfo`](docs/api_reference.md#fileinfo).
-
-### EntityInfo and TextIndex
-
-[`EntityInfo`](docs/api_reference.md#entityinfo) appears in `DeidentifyTextResponse.getEntities()`. Each entry includes the detected entity type, original value, replacement token, character positions ([`TextIndex`](docs/api_reference.md#textindex)), and confidence scores.
-
-```java
-DeidentifyTextResponse response = skyflowClient.detect("").deidentifyText(request);
-
-for (EntityInfo entity : response.getEntities()) {
- System.out.println("Entity : " + entity.getEntity());
- System.out.println("Value : " + entity.getValue());
- System.out.println("Token : " + entity.getToken());
- System.out.println("Start : " + entity.getTextIndex().getStart());
- System.out.println("End : " + entity.getTextIndex().getEnd());
- System.out.println("Score : " + entity.getScores().get(entity.getEntity()));
-}
-```
-
-### FileEntityInfo and FileInfo
-
-[`FileEntityInfo`](docs/api_reference.md#fileentityinfo) appears in `DeidentifyFileResponse.getEntities()`. [`FileInfo`](docs/api_reference.md#fileinfo) is returned by `DeidentifyFileResponse.getFile()` and contains file metadata.
-
-## Detect enums
-
-See the API Reference for full value descriptions: [`TokenType`](docs/api_reference.md#tokentype), [`DeidentifyFileStatus`](docs/api_reference.md#deidentifyfilestatus), [`DetectOutputTranscriptions`](docs/api_reference.md#detectoutputtranscriptions), [`MaskingMethod`](docs/api_reference.md#maskingmethod), [`DetectEntities`](docs/api_reference.md#detectentities).
-
-### TokenType
-
-[`TokenType`](docs/api_reference.md#tokentype) controls how detected entities are tokenized. Used in `TokenFormat.builder()`.
-
-```java
-import com.skyflow.enums.TokenType;
-
-TokenFormat tokenFormat = TokenFormat.builder()
- .vaultToken(vaultTokenList) // uses VAULT_TOKEN
- .entityOnly(entityOnlyList) // uses ENTITY_ONLY
- .entityUniqueCounter(entityUniqueCounterList) // uses ENTITY_UNIQUE_COUNTER
- .build();
-```
-
-### DeidentifyFileStatus
-
-[`DeidentifyFileStatus`](docs/api_reference.md#deidentifyfilestatus) is returned in `DeidentifyFileResponse.getStatus()` to indicate async processing state.
-
-```java
-import com.skyflow.enums.DeidentifyFileStatus;
-
-DeidentifyFileResponse response = skyflowClient.detect("").getDetectRun(request);
-if (DeidentifyFileStatus.SUCCESS.value().equals(response.getStatus())) {
- // safe to read response.getFile()
-} else if (DeidentifyFileStatus.IN_PROGRESS.value().equals(response.getStatus())) {
- // poll again using the runId
-}
-```
-
-### DetectOutputTranscriptions
-
-[`DetectOutputTranscriptions`](docs/api_reference.md#detectoutputtranscriptions) controls the transcription format for audio file deidentification.
-
-```java
-import com.skyflow.enums.DetectOutputTranscriptions;
-
-DeidentifyFileRequest request = DeidentifyFileRequest.builder()
- .file(fileInput)
- .outputTranscription(DetectOutputTranscriptions.TRANSCRIPTION)
- .build();
-```
-
-# Connections
-
-Skyflow Connections is a gateway service that uses tokenization to securely send and receive data between your systems and first- or third-party services. The [connections](https://github.com/skyflowapi/skyflow-java/tree/main/src/main/java/com/skyflow/vault/connection) module invokes both inbound and/or outbound connections.
-
-- **Inbound connections**: Act as intermediaries between your client and server, tokenizing sensitive data before it reaches your backend, ensuring downstream services handle only tokenized data.
-- **Outbound connections**: Enable secure extraction of data from the vault and transfer it to third-party services via your backend server, such as processing checkout or card issuance flows.
-
-## ConnectionController
-
-`ConnectionController` is the class returned by `skyflowClient.connection()` and `skyflowClient.connection(connectionId)`. All connection operations are called on this object.
-
-```java
-// Uses the default (first configured) connection
-ConnectionController connection = skyflowClient.connection();
-
-// Uses a specific connection by ID
-ConnectionController connection = skyflowClient.connection("");
-```
-
-**Methods:**
-
-| Method | Parameters | Returns | Description |
-|--------|-----------|---------|-------------|
-| `invoke(InvokeConnectionRequest)` | [`InvokeConnectionRequest`](docs/api_reference.md#invokeconnectionrequest) | [`InvokeConnectionResponse`](docs/api_reference.md#invokeconnectionresponse) | Invoke an inbound or outbound connection |
-
-## Invoke a connection
-
-To invoke a connection, use the `invoke` method of the Skyflow client.
-
-### Construct an invoke connection request
-
-```java
-import com.skyflow.enums.RequestMethod;
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.connection.InvokeConnectionRequest;
-import com.skyflow.vault.connection.InvokeConnectionResponse;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * This example demonstrates how to invoke an external connection using the Skyflow SDK, along with corresponding InvokeConnectionRequest schema.
- *
- */
-public class InvokeConnectionSchema {
- public static void main(String[] args) {
- try {
- // Initialize Skyflow client
- // Step 1: Define the request body parameters
- // These are the values you want to send in the request body
- Map requestBody = new HashMap<>();
- requestBody.put("", "");
- requestBody.put("", "");
-
- // Step 2: Define the request headers
- // Add any required headers that need to be sent with the request
- Map requestHeaders = new HashMap<>();
- requestHeaders.put("", "");
- requestHeaders.put("", "");
-
- // Step 3: Define the path parameters
- // Path parameters are part of the URL and typically used in RESTful APIs
- Map pathParams = new HashMap<>();
- pathParams.put("", "");
- pathParams.put("", "");
-
- // Step 4: Define the query parameters
- // Query parameters are included in the URL after a '?' and are used to filter or modify the response
- Map queryParams = new HashMap<>();
- queryParams.put("", "");
- queryParams.put("", "");
-
- // Step 5: Build the InvokeConnectionRequest using the provided parameters
- InvokeConnectionRequest invokeConnectionRequest = InvokeConnectionRequest.builder()
- .method(RequestMethod.POST) // The HTTP method to use for the request (POST in this case)
- .requestBody(requestBody) // The body of the request
- .requestHeaders(requestHeaders) // The headers to include in the request
- .pathParams(pathParams) // The path parameters for the URL
- .queryParams(queryParams) // The query parameters to append to the URL
- .build();
-
- // Step 6: Invoke the connection using the request
- // Replace "" with the actual connection ID you are using
- InvokeConnectionResponse invokeConnectionResponse = skyflowClient.connection("").invoke(invokeConnectionRequest);
-
- // Step 7: Print the response from the invoked connection
- // This response contains the result of the request sent to the external system
- System.out.println(invokeConnectionResponse);
-
- } catch (SkyflowException e) {
- // Step 8: Handle any exceptions that occur during the connection invocation
- System.out.println("Error occurred: ");
- e.printStackTrace(); // Print the exception stack trace for debugging
- }
- }
-}
-```
-
-`method` accepts any [`RequestMethod`](docs/api_reference.md#requestmethod) value (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`). See [`InvokeConnectionRequest`](docs/api_reference.md#invokeconnectionrequest) in the API Reference for all builder options.
-
-**pathParams, queryParams, requestHeader, requestBody** are the JSON objects represented as HashMaps, that will be sent through the connection integration url.
-
-### An [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/connection/InvokeConnectionExample.java) of invokeConnection
-
-```java
-import com.skyflow.Skyflow;
-import com.skyflow.config.ConnectionConfig;
-import com.skyflow.config.Credentials;
-import com.skyflow.enums.LogLevel;
-import com.skyflow.enums.RequestMethod;
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.connection.InvokeConnectionRequest;
-import com.skyflow.vault.connection.InvokeConnectionResponse;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * This example demonstrates how to invoke an external connection using the Skyflow SDK.
- * It configures a connection, sets up the request, and sends a POST request to the external service.
- *
- * 1. Initialize Skyflow client with connection details.
- * 2. Define the request body, headers, and method.
- * 3. Execute the connection request.
- * 4. Print the response from the invoked connection.
- */
-public class InvokeConnectionExample {
- public static void main(String[] args) {
- try {
- // Initialize Skyflow client
- // Step 1: Set up credentials and connection configuration
- // Load credentials from a JSON file (you need to provide the correct path)
- Credentials credentials = new Credentials();
- credentials.setPath("/path/to/credentials.json");
-
- // Define the connection configuration (URL and credentials)
- ConnectionConfig connectionConfig = new ConnectionConfig();
- connectionConfig.setConnectionId(""); // Replace with actual connection ID
- connectionConfig.setConnectionUrl("https://connection.url.com"); // Replace with actual connection URL
- connectionConfig.setCredentials(credentials); // Set credentials for the connection
-
- // Initialize the Skyflow client with the connection configuration
- Skyflow skyflowClient = Skyflow.builder()
- .setLogLevel(LogLevel.DEBUG) // Set log level to DEBUG for detailed logs
- .addConnectionConfig(connectionConfig) // Add connection configuration to client
- .build(); // Build the Skyflow client instance
-
- // Step 2: Define the request body and headers
- // Map for request body parameters
- Map requestBody = new HashMap<>();
- requestBody.put("card_number", "4337-1696-5866-0865"); // Example card number
- requestBody.put("ssn", "524-41-4248"); // Example SSN
-
- // Map for request headers
- Map requestHeaders = new HashMap<>();
- requestHeaders.put("Content-Type", "application/json"); // Set content type for the request
-
- // Step 3: Build the InvokeConnectionRequest with required parameters
- // Set HTTP method to POST, include the request body and headers
- InvokeConnectionRequest invokeConnectionRequest = InvokeConnectionRequest.builder()
- .method(RequestMethod.POST) // HTTP POST method
- .requestBody(requestBody) // Add request body parameters
- .requestHeaders(requestHeaders) // Add headers
- .build(); // Build the request
-
- // Step 4: Invoke the connection and capture the response
- // Replace "" with the actual connection ID
- InvokeConnectionResponse invokeConnectionResponse = skyflowClient.connection("").invoke(invokeConnectionRequest);
-
- // Step 5: Print the response from the connection invocation
- System.out.println(invokeConnectionResponse); // Print the response to the console
-
- } catch (SkyflowException e) {
- // Step 6: Handle any exceptions that occur during the connection invocation
- System.out.println("Error occurred: ");
- e.printStackTrace(); // Print the exception stack trace for debugging
- }
- }
-}
-```
-
-Sample response:
-
-```json
-{
- "data": {
- "card_number": "4337-1696-5866-0865",
- "ssn": "524-41-4248"
- },
- "metadata": {
- "requestId": "4a3453b5-7aa4-4373-98d7-cf102b1f6f97"
- }
-}
-```
-
-# Authenticate with bearer tokens
-
-This section covers methods for generating and managing tokens to authenticate API calls:
-
-- **Generate a bearer token**:
- Enable the creation of bearer tokens using service account credentials. These tokens, valid for 60 minutes, provide secure access to Vault services and management APIs based on the service account's permissions. Use this for general API calls when you only need basic authentication without additional context or role-based restrictions.
-- **Generate a bearer token with context**:
- Support embedding context values into bearer tokens, enabling dynamic access control and the ability to track end-user identity. These tokens include context claims and allow flexible authorization for Vault services. Use this when policies depend on specific contextual attributes or when tracking end-user identity is required.
-- **Generate a scoped bearer token**:
- Facilitate the creation of bearer tokens with role-specific access, ensuring permissions are limited to the operations allowed by the designated role. This is particularly useful for service accounts with multiple roles. Use this to enforce fine-grained role-based access control, ensuring tokens only grant permissions for a specific role.
-- **Generate signed data tokens**:
- Add an extra layer of security by digitally signing data tokens with the service account's private key. These signed tokens can be securely detokenized, provided the necessary bearer token and permissions are available. Use this to add cryptographic protection to sensitive data, enabling secure detokenization with verified integrity and authenticity.
-
-## Generate a bearer token
-
-The [Service Account](https://github.com/skyflowapi/skyflow-java/tree/main/src/main/java/com/skyflow/serviceaccount/util) Java module generates service account tokens using a service account credentials file, which is provided when a service account is created. The tokens generated by this module are valid for 60 minutes and can be used to make API calls to the [Data](https://docs.skyflow.com/record/) and [Management](https://docs.skyflow.com/management/) APIs, depending on the permissions assigned to the service account.
-
-The `BearerToken` utility class generates bearer tokens using a credentials JSON file. Alternatively, you can pass the credentials as a string.
-
-[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java):
-
-```java
-/**
- * Example program to generate a Bearer Token using Skyflow's BearerToken utility.
- * The token can be generated in two ways:
- * 1. Using the file path to a credentials.json file.
- * 2. Using the JSON content of the credentials file as a string.
- */
-public class BearerTokenGenerationExample {
- public static void main(String[] args) {
- // Variable to store the generated token
- String token = null;
-
- // Example 1: Generate Bearer Token using a credentials.json file
- try {
- // Specify the full file path to the credentials.json file
- String filePath = "";
-
- // Check if the token is either not initialized or has expired
- if (Token.isExpired(token)) {
- // Create a BearerToken object using the credentials file
- BearerToken bearerToken = BearerToken.builder()
- .setCredentials(new File(filePath)) // Set credentials from the file path
- .build();
-
- // Generate a new Bearer Token
- token = bearerToken.getBearerToken();
- }
-
- // Print the generated Bearer Token to the console
- System.out.println("Generated Bearer Token (from file): " + token);
- } catch (SkyflowException e) {
- // Handle any exceptions encountered during the token generation process
- e.printStackTrace();
- }
-
- // Example 2: Generate Bearer Token using the credentials JSON as a string
- try {
- // Provide the credentials JSON content as a string
- String fileContents = "";
-
- // Check if the token is either not initialized or has expired
- if (Token.isExpired(token)) {
- // Create a BearerToken object using the credentials string
- BearerToken bearerToken = BearerToken.builder()
- .setCredentials(fileContents) // Set credentials from the string
- .build();
-
- // Generate a new Bearer Token
- token = bearerToken.getBearerToken();
- }
-
- // Print the generated Bearer Token to the console
- System.out.println("Generated Bearer Token (from string): " + token);
- } catch (SkyflowException e) {
- // Handle any exceptions encountered during the token generation process
- e.printStackTrace();
- }
- }
-}
-```
-
-## Generate bearer tokens with context
-
-**Context-aware authorization** embeds context values into a bearer token during its generation so you can reference those values in your policies. This enables more flexible access controls, such as helping you track end-user identity when making API calls using service accounts, and facilitates using signed data tokens during detokenization.
-
-A service account with the `context_id` identifier generates bearer tokens containing context information, represented as a JWT claim in a Skyflow-generated bearer token. Tokens generated from such service accounts include a `context_identifier` claim, are valid for 60 minutes, and can be used to make API calls to the Data and Management APIs, depending on the service account's permissions.
-
-The `setCtx()` method accepts either a **String** or a **`Map`**:
-
-**String context** — use when your policy references a single context value:
-
-```java
-BearerToken token = BearerToken.builder()
- .setCredentials(new File(filePath))
- .setCtx("user_12345")
- .build();
-```
-
-**JSON object context** — use when your policy needs multiple context values for conditional data access. Each key in the `Map` maps to a Skyflow CEL policy variable under `request.context.*`:
-
-```java
-Map ctx = new HashMap<>();
-ctx.put("role", "admin");
-ctx.put("department", "finance");
-ctx.put("user_id", "user_12345");
-
-BearerToken token = BearerToken.builder()
- .setCredentials(new File(filePath))
- .setCtx(ctx)
- .build();
-```
-
-With the map above, your Skyflow policies can reference `request.context.role`, `request.context.department`, and `request.context.user_id` to make conditional access decisions.
-
-You can also set context on `Credentials` for automatic token generation:
-
-```java
-// String context
-Credentials credentials = new Credentials();
-credentials.setPath("path/to/credentials.json");
-credentials.setContext("user_12345");
-
-// Map context
-Map ctx = new HashMap<>();
-ctx.put("role", "admin");
-ctx.put("department", "finance");
-credentials.setContext(ctx);
-```
-
-> **Note:** `getContext()` returns `Object` — callers should use `instanceof` if they need to inspect the type.
-
-Context map keys must contain only alphanumeric characters and underscores (`[a-zA-Z0-9_]`). Invalid keys will throw a `SkyflowException`.
-
-[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java)
-
-See Skyflow's [context-aware authorization](https://docs.skyflow.com) and [conditional data access](https://docs.skyflow.com) docs for policy variable syntax like `request.context.*`.
-
-## Generate scoped bearer tokens
-
-A service account with multiple roles can generate bearer tokens with access limited to a specific role by specifying the appropriate `roleID`. This can be used to limit access to specific roles for services with multiple responsibilities, such as segregating access for billing and analytics. The generated bearer tokens are valid for 60 minutes and can only execute operations permitted by the permissions associated with the designated role.
-
-[Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java):
-
-```java
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.serviceaccount.util.BearerToken;
-
-import java.io.File;
-import java.util.ArrayList;
-
-/**
- * Example program to generate a Scoped Token using Skyflow's BearerToken utility.
- * The token is generated by providing the file path to the credentials.json file
- * and specifying roles associated with the token.
- */
-public class ScopedTokenGenerationExample {
- public static void main(String[] args) {
- // Variable to store the generated scoped token
- String scopedToken = null;
-
- // Example: Generate Scoped Token by specifying the credentials.json file path
- try {
- // Create a list of roles that the generated token will be scoped to
- ArrayList roles = new ArrayList<>();
- roles.add("ROLE_ID"); // Add a specific role to the list (e.g., "ROLE_ID")
-
- // Specify the full file path to the service account's credentials.json file
- String filePath = "";
-
- // Create a BearerToken object using the credentials file and associated roles
- BearerToken bearerToken = BearerToken.builder()
- .setCredentials(new File(filePath)) // Set credentials using the credentials.json file
- .setRoles(roles) // Set the roles that the token should be scoped to
- .build(); // Build the BearerToken object
-
- // Retrieve the generated scoped token
- scopedToken = bearerToken.getBearerToken();
-
- // Print the generated scoped token to the console
- System.out.println(scopedToken);
- } catch (SkyflowException e) {
- // Handle exceptions that may occur during token generation
- e.printStackTrace();
- }
- }
-}
-```
-
-Notes:
-
-- You can pass either the file path of a service account key credentials file or the service account key credentials as a string to the `setCredentials` method of the `BearerTokenBuilder` class.
-- If both a file path and a string are provided, the last method used takes precedence.
-- To generate multiple bearer tokens concurrently using threads, refer to the following [example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java).
-
-## Generate Signed Data Tokens
-
-Skyflow generates data tokens when sensitive data is inserted into the vault. These data tokens can be digitally signed
-with the private key of the service account credentials, which adds an additional layer of protection. Signed tokens can
-be detokenized by passing the signed data token and a bearer token generated from service account credentials. The
-service account must have appropriate permissions and context to detokenize the signed data tokens.
-
-The `setCtx()` method on `SignedDataTokensBuilder` also accepts either a **String** or a **`Map`**, using the same format as bearer tokens:
-
-```java
-// String context
-SignedDataTokens signedToken = SignedDataTokens.builder()
- .setCredentials(new File(filePath))
- .setCtx("user_12345")
- .setTimeToLive(30)
- .setDataTokens(dataTokens)
- .build();
-
-// JSON object context
-Map ctx = new HashMap<>();
-ctx.put("role", "analyst");
-ctx.put("department", "research");
-
-SignedDataTokens signedToken = SignedDataTokens.builder()
- .setCredentials(new File(filePath))
- .setCtx(ctx)
- .setTimeToLive(30)
- .setDataTokens(dataTokens)
- .build();
-```
-
-[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java)
-
-Response:
-
-```json
-[
- {
- "dataToken": "5530-4316-0674-5748",
- "signedDataToken": "signed_token_eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzLCpZjA"
- }
-]
-```
-
-Notes:
-
-- You can provide either the file path to a service account key credentials file or the service account key credentials as a string to the `setCredentials` method of the `SignedDataTokensBuilder` class.
-- If both a file path and a string are passed to the `setCredentials` method, the most recently specified input takes precedence.
-- The `time-to-live` (TTL) value should be specified in seconds.
-- By default, the TTL value is set to 60 seconds.
-
-## Bearer token expiry edge case
-When you use bearer tokens for authentication and API requests in SDKs, there's the potential for a token to expire after the token is verified as valid but before the actual API call is made, causing the request to fail unexpectedly due to the token's expiration. An error from this edge case would look something like this:
-
-```txt
-message: Authentication failed. Bearer token is expired. Use a valid bearer token. See https://docs.skyflow.com/api-authentication/
-```
-
-If you encounter this kind of error, retry the request. During the retry, the SDK detects that the previous bearer token has expired and generates a new one for the current and subsequent requests.
-
-#### [Example](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java):
-
-```java
-package com.example.serviceaccount;
-
-import com.skyflow.Skyflow;
-import com.skyflow.config.Credentials;
-import com.skyflow.config.VaultConfig;
-import com.skyflow.enums.Env;
-import com.skyflow.enums.LogLevel;
-import com.skyflow.enums.RedactionType;
-import com.skyflow.errors.SkyflowException;
-import com.skyflow.vault.tokens.DetokenizeRequest;
-import com.skyflow.vault.tokens.DetokenizeResponse;
-import io.github.cdimascio.dotenv.Dotenv;
-import java.util.ArrayList;
-
-/**
- * This example demonstrates how to configure and use the Skyflow SDK
- * to detokenize sensitive data stored in a Skyflow vault.
- * It includes setting up credentials, configuring the vault, and
- * making a detokenization request. The code also implements a retry
- * mechanism to handle unauthorized access errors (HTTP 401).
- */
-public class DetokenizeExample {
- public static void main(String[] args) {
- try {
- // Setting up credentials for accessing the Skyflow vault
- Credentials vaultCredentials = new Credentials();
- vaultCredentials.setCredentialsString("");
-
- // Configuring the Skyflow vault with necessary details
- VaultConfig vaultConfig = new VaultConfig();
- vaultConfig.setVaultId(""); // Vault ID
- vaultConfig.setClusterId(""); // Cluster ID
- vaultConfig.setEnv(Env.PROD); // Environment (e.g., DEV, PROD)
- vaultConfig.setCredentials(vaultCredentials); // Setting credentials
-
- // Creating a Skyflow client instance with the configured vault
- Skyflow skyflowClient = Skyflow.builder()
- .setLogLevel(LogLevel.ERROR) // Setting log level to ERROR
- .addVaultConfig(vaultConfig) // Adding vault configuration
- .build();
-
- // Attempting to detokenize data using the Skyflow client
- try {
- detokenizeData(skyflowClient);
- } catch (SkyflowException e) {
- // Retry detokenization if the error is due to unauthorized access (HTTP 401)
- if (e.getHttpCode() == 401) {
- detokenizeData(skyflowClient);
- } else {
- // Rethrow the exception for other error codes
- throw e;
- }
- }
- } catch (SkyflowException e) {
- // Handling any exceptions that occur during the process
- System.out.println("An error occurred: " + e.getMessage());
- }
- }
-
- /**
- * Method to detokenize data using the Skyflow client.
- * It sends a detokenization request with a list of tokens and prints the response.
- *
- * @param skyflowClient The Skyflow client instance used for detokenization.
- * @throws SkyflowException If an error occurs during the detokenization process.
- */
- public static void detokenizeData(Skyflow skyflowClient) throws SkyflowException {
- // Creating a list of tokens to be detokenized
- ArrayList tokenList = new ArrayList<>();
- tokenList.add(""); // First token
- tokenList.add(""); // Second token
-
- // Building a detokenization request with the token list and configuration
- DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder()
- .tokens(tokenList) // Adding tokens to the request
- .continueOnError(false) // Stop on error
- .redactionType(RedactionType.PLAIN_TEXT) // Redaction type (e.g., PLAIN_TEXT)
- .build();
-
- // Sending the detokenization request and receiving the response
- DetokenizeResponse detokenizeResponse = skyflowClient.vault().detokenize(detokenizeRequest);
-
- // Printing the detokenized response
- System.out.println(detokenizeResponse);
- }
-}
-```
-
-# Client Management
-
-After the `Skyflow` client is built you can add, retrieve, update, or remove vault and connection configurations at runtime — without rebuilding the client.
-
-## Vault configuration management
-
-```java
-import com.skyflow.config.VaultConfig;
-
-// Add a new vault at runtime
-skyflowClient.addVaultConfig(newVaultConfig);
-
-// Retrieve the config for a specific vault
-VaultConfig config = skyflowClient.getVaultConfig("");
-
-// Update an existing vault config (match by vaultId)
-skyflowClient.updateVaultConfig(updatedVaultConfig);
-
-// Remove a vault from the client
-skyflowClient.removeVaultConfig("");
-```
-
-## Connection configuration management
-
-```java
-import com.skyflow.config.ConnectionConfig;
-
-// Add a new connection at runtime
-skyflowClient.addConnectionConfig(newConnectionConfig);
-
-// Retrieve the config for a specific connection
-ConnectionConfig config = skyflowClient.getConnectionConfig("");
-
-// Update an existing connection config (match by connectionId)
-skyflowClient.updateConnectionConfig(updatedConnectionConfig);
-
-// Remove a connection from the client
-skyflowClient.removeConnectionConfig("");
-```
-
-## Credentials and log level management
-
-```java
-// Replace the Skyflow-level credentials used when vault/connection configs
-// do not specify their own credentials
-skyflowClient.updateSkyflowCredentials(newCredentials);
-
-// Update the log level after the client has been built
-skyflowClient.updateLogLevel(LogLevel.DEBUG);
-
-// Read the current log level
-LogLevel currentLevel = skyflowClient.getLogLevel();
-```
-
-**Client management method reference:**
-
-| Method | Returns | Description |
-|--------|---------|-------------|
-| `addVaultConfig(VaultConfig)` | `Skyflow` | Add a vault configuration |
-| `getVaultConfig(String vaultId)` | `VaultConfig` | Retrieve a vault configuration by ID |
-| `updateVaultConfig(VaultConfig)` | `Skyflow` | Replace a vault configuration (matched by `vaultId`) |
-| `removeVaultConfig(String vaultId)` | `Skyflow` | Remove a vault configuration |
-| `addConnectionConfig(ConnectionConfig)` | `Skyflow` | Add a connection configuration |
-| `getConnectionConfig(String connectionId)` | `ConnectionConfig` | Retrieve a connection configuration by ID |
-| `updateConnectionConfig(ConnectionConfig)` | `Skyflow` | Replace a connection configuration |
-| `removeConnectionConfig(String connectionId)` | `Skyflow` | Remove a connection configuration |
-| `updateSkyflowCredentials(Credentials)` | `Skyflow` | Replace the client-level credentials |
-| `updateLogLevel(LogLevel)` | `Skyflow` | Change the log level after initialization |
-| `getLogLevel()` | `LogLevel` | Return the current log level |
-
-All mutating methods return the `Skyflow` instance for chaining and throw `SkyflowException` on validation errors.
-
-# Error Handling
-
-The SDK uses `SkyflowException` for all errors — both client-side validation errors and server-side API errors.
-
-## Catching SkyflowException
-
-Wrap SDK calls in a `try/catch` block and catch `SkyflowException` to handle Skyflow-specific errors separately from unexpected exceptions:
-
-```java
-import com.skyflow.errors.SkyflowException;
-
-try {
- InsertResponse response = skyflowClient.vault().insert(insertRequest);
-} catch (SkyflowException e) {
- System.err.println("Skyflow error:");
- System.err.println(" HTTP code : " + e.getHttpCode());
- System.err.println(" Message : " + e.getMessage());
- System.err.println(" Request ID: " + e.getRequestId());
- System.err.println(" Details : " + e.getDetails());
-} catch (Exception e) {
- System.err.println("Unexpected error: " + e.getMessage());
-}
-```
-
-## SkyflowException properties
-
-| Property | Method | Description |
-|---|---|---|
-| HTTP status code | `getHttpCode()` | Integer status code (e.g. `400`, `404`, `500`). |
-| Message | `getMessage()` | Human-readable description of the error. |
-| HTTP status string | `getHttpStatus()` | Status string from the server (e.g. `"BAD_REQUEST"`). |
-| gRPC code | `getGrpcCode()` | gRPC status code from the server. |
-| Request ID | `getRequestId()` | The `x-request-id` header — useful for support escalations. |
-| Details | `getDetails()` | `JsonArray` of additional error context from the server. Empty array for validation errors, `null` if the server response omitted the field. |
-
-**Validation errors** (missing table name, empty token list, etc.) are thrown before any network call:
-- `httpCode` is always `400`
-- `requestId` and `grpcCode` are `null`
-- `details` is an empty array
-
-**API errors** are returned by the Skyflow server and have all fields populated from the response body and headers.
-
-# Logging
-
-The SDK provides logging with Java's built-in logging library. By default, the SDK's logging level is set to `LogLevel.ERROR`. This can be changed using the `setLogLevel(logLevel)` method, as shown below:
-
-Currently, the following five log levels are supported:
+## Which package do I want?
-- `DEBUG`**:**
- When `LogLevel.DEBUG` is passed, logs at all levels will be printed (DEBUG, INFO, WARN, ERROR).
-- `INFO`**:**
- When `LogLevel.INFO` is passed, INFO logs for every event that occurs during SDK flow execution will be printed, along with WARN and ERROR logs.
-- `WARN`**:**
- When `LogLevel.WARN` is passed, only WARN and ERROR logs will be printed.
-- `ERROR`**:**
- When `LogLevel.ERROR` is passed, only ERROR logs will be printed.
-- `OFF`**:**
- `LogLevel.OFF` can be used to turn off all logging from the Skyflow Java SDK.
+| Package | Artifact | README | Vault Type | Version line |
+|---|---|---|---|---|
+| **skyvault** | `com.skyflow:skyflow-java` | [skyvault/README.md](skyvault/README.md) | Privacy DB | 2.x |
+| **flowvault** | `com.skyflow:skyflow-flowvault-java` | [flowvault/README.md](flowvault/README.md) | Flow DB | 1.x |
-**Note:** The ranking of logging levels is as follows: `DEBUG` \< `INFO` \< `WARN` \< `ERROR` \< `OFF`.
+`flowvault` shares auth/client setup with `skyvault` — both depend on the `common` module.
-```java
-import com.skyflow.Skyflow;
-import com.skyflow.config.Credentials;
-import com.skyflow.config.VaultConfig;
-import com.skyflow.enums.Env;
-import com.skyflow.enums.LogLevel;
-import com.skyflow.errors.SkyflowException;
+> **The two artifacts are versioned independently.** `flowvault` is a new SDK starting at `1.0.0`; its lower version number reflects a first release, not an older or lesser SDK than `skyvault` 2.x. Upgrade each on its own version line.
-/**
- * This example demonstrates how to configure the Skyflow client with custom log levels
- * and authentication credentials (either token, credentials string, or other methods).
- * It also shows how to configure a vault connection using specific parameters.
- *
- * 1. Set up credentials with a Bearer token or credentials string.
- * 2. Define the Vault configuration.
- * 3. Build the Skyflow client with the chosen configuration and set log level.
- * 4. Example of changing the log level from ERROR (default) to INFO.
- */
-public class ChangeLogLevel {
- public static void main(String[] args) throws SkyflowException {
- // Step 1: Set up credentials - either pass token or use credentials string
- // In this case, we are using a Bearer token for authentication
- Credentials credentials = new Credentials();
- credentials.setToken(""); // Replace with actual Bearer token
+> Migrating from v1? See skyvault's **[Migration Guide](docs/migrate_to_v2.md)**. V1 is in maintenance mode and will reach End of Life on October 31, 2026.
- // Step 2: Define the Vault configuration
- // Configure the vault with necessary details like vault ID, cluster ID, and environment
- VaultConfig config = new VaultConfig();
- config.setVaultId(""); // Replace with actual Vault ID (primary vault)
- config.setClusterId(""); // Replace with actual Cluster ID (from vault URL)
- config.setEnv(Env.PROD); // Set the environment (default is PROD)
- config.setCredentials(credentials); // Set credentials for the vault (either token or credentials)
+## Repository layout
- // Step 3: Define additional Skyflow credentials (optional, if needed for credentials string)
- // Create a JSON object to hold your Skyflow credentials
- JsonObject credentialsObject = new JsonObject();
- credentialsObject.addProperty("clientId", ""); // Replace with your client ID
- credentialsObject.addProperty("clientName", ""); // Replace with your client name
- credentialsObject.addProperty("tokenUri", ""); // Replace with your token URI
- credentialsObject.addProperty("keyId", ""); // Replace with your key ID
- credentialsObject.addProperty("privateKey", ""); // Replace with your private key
+The root `pom.xml` (`packaging=pom`) aggregates this Maven reactor:
- // Convert the credentials object to a string format to be used for generating a Bearer Token
- Credentials skyflowCredentials = new Credentials();
- skyflowCredentials.setCredentialsString(credentialsObject.toString()); // Set credentials string
+- `common/` — shared client, credentials, config, and error-handling code used by both `skyvault` and `flowvault`
+- `skyvault/` — the `skyflow-java` SDK ([README](skyvault/README.md))
+- `flowvault/` — the `skyflow-flowvault-java` SDK ([README](flowvault/README.md))
- // Step 4: Build the Skyflow client with the chosen configuration and log level
- Skyflow skyflowClient = Skyflow.builder()
- .addVaultConfig(config) // Add the Vault configuration
- .addSkyflowCredentials(skyflowCredentials) // Use Skyflow credentials if no token is passed
- .setLogLevel(LogLevel.INFO) // Set log level to INFO (default is ERROR)
- .build(); // Build the Skyflow client
+## Documentation
- // Now, the Skyflow client is ready to use with the specified log level and credentials
- System.out.println("Skyflow client has been successfully configured with log level: INFO.");
- }
-}
-```
+- [skyvault API Reference](docs/api_reference.md) — full list of request builder methods, response getters, enums, and service-account utilities
+- [Migrate from v1 to v2](docs/migrate_to_v2.md)
-# Reporting a Vulnerability
+## Reporting a Vulnerability
If you discover a potential security issue in this project, please reach out to us at **security@skyflow.com**. Please do not create public GitHub issues or Pull Requests, as malicious actors could potentially view them.
diff --git a/common/src/main/java/com/skyflow/BaseSkyflow.java b/common/src/main/java/com/skyflow/BaseSkyflow.java
index a60dc02b..d02dc45a 100644
--- a/common/src/main/java/com/skyflow/BaseSkyflow.java
+++ b/common/src/main/java/com/skyflow/BaseSkyflow.java
@@ -69,7 +69,10 @@ protected static T resolveOrThrow(Map map, String key,
ErrorLogs errorLog, ErrorMessage errorMessage) throws SkyflowException {
T value = key != null ? map.get(key) : map.values().stream().findFirst().orElse(null);
if (value == null) {
- LogUtil.printErrorLog(errorLog.getLog());
+ // The log line carries a %s1 placeholder for the id. Callers that resolve the single
+ // configured entry pass no key, so say so rather than emitting the raw placeholder.
+ LogUtil.printErrorLog(BaseUtils.parameterizedString(
+ errorLog.getLog(), key != null ? key : "not specified"));
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), errorMessage.getMessage());
}
return value;
diff --git a/common/src/main/java/com/skyflow/errors/SkyflowException.java b/common/src/main/java/com/skyflow/errors/SkyflowException.java
index 676bdd74..2b043f92 100644
--- a/common/src/main/java/com/skyflow/errors/SkyflowException.java
+++ b/common/src/main/java/com/skyflow/errors/SkyflowException.java
@@ -9,6 +9,33 @@
import java.util.List;
import java.util.Map;
+/**
+ * Exception thrown by all Skyflow SDK operations.
+ *
+ * There are two broad categories of errors:
+ *
+ *
+ * - Validation errors — caught before any network call is made (e.g. missing table,
+ * empty token list). These always have {@code httpCode = 400} and an empty
+ * {@link #getDetails()} array. {@link #getRequestId()} and {@link #getGrpcCode()} are
+ * {@code null}.
+ *
- API errors — returned by the Skyflow server. The HTTP status code, gRPC code,
+ * human-readable status string, error message, and request ID are all parsed from the
+ * response and available via the corresponding getters.
+ *
+ *
+ * Typical error-handling pattern:
+ *
{@code
+ * try {
+ * response = vault.insert(request);
+ * } catch (SkyflowException e) {
+ * System.err.println("HTTP " + e.getHttpCode() + " — " + e.getMessage());
+ * if (e.getRequestId() != null) {
+ * System.err.println("Request ID: " + e.getRequestId());
+ * }
+ * }
+ * }
+ */
public class SkyflowException extends Exception {
private String requestId;
private Integer grpcCode;
@@ -33,6 +60,11 @@ public SkyflowException(String message, Throwable cause) {
this.message = message;
}
+ /**
+ * Constructs a validation error with a fixed HTTP 400 status.
+ * {@link #getDetails()} returns an empty array; {@link #getRequestId()} and
+ * {@link #getGrpcCode()} return {@code null}.
+ */
public SkyflowException(int code, String message) {
super(message);
this.httpCode = code;
@@ -41,6 +73,13 @@ public SkyflowException(int code, String message) {
this.details = new JsonArray();
}
+ /**
+ * Constructs an API error from an HTTP response.
+ * Parses the JSON error body to populate {@link #getMessage()}, {@link #getGrpcCode()},
+ * {@link #getHttpStatus()}, and {@link #getDetails()}. The request ID is read from the
+ * {@code x-request-id} response header. If the body cannot be parsed, falls back to the
+ * raw body string as the message.
+ */
public SkyflowException(int httpCode, Throwable cause, Map> responseHeaders, String responseBody) {
super(cause);
this.httpCode = httpCode > 0 ? httpCode : 400;
@@ -65,6 +104,10 @@ private void setResponseBody(String responseBody, Map> resp
}
}
+ /**
+ * Returns the {@code x-request-id} from the server response, useful for support escalations.
+ * {@code null} for validation errors that never reached the server.
+ */
public String getRequestId() {
return requestId;
}
@@ -89,10 +132,20 @@ private void setHttpStatus() {
this.httpStatus = statusElement == null ? null : statusElement.getAsString();
}
+ /**
+ * Returns the HTTP status code (e.g. 400, 404, 500).
+ * Defaults to 400 when the server returned a non-positive code, and 0 when the
+ * exception carries no HTTP code at all (e.g. it wraps a local failure).
+ */
public int getHttpCode() {
return httpCode == null ? 0 : httpCode;
}
+ /**
+ * Returns additional error details from the server response, or an empty array for
+ * validation errors. Never {@code null} for validation errors; may be {@code null} for
+ * API errors whose response body contained no {@code details} field.
+ */
public JsonArray getDetails() {
return details;
}
@@ -112,10 +165,18 @@ private void setDetails(Map> responseHeaders) {
}
}
+ /**
+ * Returns the gRPC status code from the server response.
+ * {@code null} for validation errors and API responses that omit this field.
+ */
public Integer getGrpcCode() {
return grpcCode;
}
+ /**
+ * Returns the human-readable HTTP status string from the server response (e.g.
+ * {@code "Bad Request"}, {@code "Not Found"}).
+ */
public String getHttpStatus() {
return httpStatus;
}
diff --git a/common/src/main/java/com/skyflow/vault/data/RequestContext.java b/common/src/main/java/com/skyflow/vault/data/RequestContext.java
deleted file mode 100644
index 0cf6055b..00000000
--- a/common/src/main/java/com/skyflow/vault/data/RequestContext.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.skyflow.vault.data;
-
-import com.skyflow.enums.CustomHeaderKey;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-public final class RequestContext {
- private final String operation;
- private final Map headers = new HashMap<>();
-
- public RequestContext(String operation) {
- this.operation = operation;
- }
-
- public String getOperation() { return operation; }
-
- public void addHeader(CustomHeaderKey key, String value) {
- headers.put(key, value);
- }
-
- public Map getHeaders() {
- return Collections.unmodifiableMap(headers);
- }
-}
diff --git a/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java b/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java
index 83df09ee..1fb01e6e 100644
--- a/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java
+++ b/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java
@@ -138,6 +138,15 @@ public void testToStringWithNullFields() {
Assert.assertTrue(str.contains("details: null"));
}
+ // Regression: skyvault used to ship its own copy of this class whose getHttpCode()
+ // unboxed the null Integer and threw NPE for every constructor that takes no code.
+ @Test
+ public void testGetHttpCodeIsZeroWhenNoCodeWasSet() {
+ Assert.assertEquals(0, new SkyflowException("local failure").getHttpCode());
+ Assert.assertEquals(0, new SkyflowException(new RuntimeException("boom")).getHttpCode());
+ Assert.assertEquals(0, new SkyflowException("local failure", new RuntimeException("boom")).getHttpCode());
+ }
+
@Test
public void testZeroHttpCodeDefaultsTo400() {
Map> headers = new HashMap<>();
diff --git a/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java b/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java
deleted file mode 100644
index 0c6700d8..00000000
--- a/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.skyflow.vault.data;
-
-import com.skyflow.enums.CustomHeaderKey;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.Map;
-
-public class RequestContextTests {
-
- @Test
- public void testGetOperationReturnsConstructorValue() {
- RequestContext context = new RequestContext("INSERT");
-
- Assert.assertEquals("INSERT", context.getOperation());
- }
-
- @Test
- public void testNullOperation() {
- RequestContext context = new RequestContext(null);
-
- Assert.assertNull(context.getOperation());
- }
-
- @Test
- public void testGetHeadersReturnsEmptyMapByDefault() {
- RequestContext context = new RequestContext("INSERT");
-
- Assert.assertTrue(context.getHeaders().isEmpty());
- }
-
- @Test
- public void testAddHeaderIsReflectedInGetHeaders() {
- RequestContext context = new RequestContext("INSERT");
- context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value");
-
- Map headers = context.getHeaders();
-
- Assert.assertEquals(1, headers.size());
- Assert.assertEquals("account-id-value", headers.get(CustomHeaderKey.SkyflowAccountId));
- }
-
- @Test
- public void testAddHeaderOverwritesExistingValueForSameKey() {
- RequestContext context = new RequestContext("INSERT");
- context.addHeader(CustomHeaderKey.SkyflowAccountId, "first-value");
- context.addHeader(CustomHeaderKey.SkyflowAccountId, "second-value");
-
- Assert.assertEquals(1, context.getHeaders().size());
- Assert.assertEquals("second-value", context.getHeaders().get(CustomHeaderKey.SkyflowAccountId));
- }
-
- @Test
- public void testAddMultipleDistinctHeaders() {
- RequestContext context = new RequestContext("DETOKENIZE");
- context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value");
- context.addHeader(CustomHeaderKey.SkyflowAccountName, "account-name-value");
-
- Assert.assertEquals(2, context.getHeaders().size());
- }
-
- @Test(expected = UnsupportedOperationException.class)
- public void testGetHeadersReturnsUnmodifiableMap() {
- RequestContext context = new RequestContext("INSERT");
-
- context.getHeaders().put(CustomHeaderKey.RequestIdHeader, "request-id-value");
- }
-}
diff --git a/flowvault/README.md b/flowvault/README.md
new file mode 100644
index 00000000..442a1fbf
--- /dev/null
+++ b/flowvault/README.md
@@ -0,0 +1,845 @@
+# Skyflow FlowVault Java SDK
+
+The `flowvault` module is a Skyflow Java SDK built for high-throughput vault operations. It shares its client, credentials, and configuration classes with the [skyvault SDK](../skyvault/README.md) (both depend on the `common` module) but exposes a different, narrower surface: **bulk** vault operations only.
+
+> Meant for **Flow DB** vaults.
+
+> **`flowvault` is a new SDK, versioned independently of `skyvault`.** It starts at `1.0.0` while `skyvault` (`com.skyflow:skyflow-java`) is at `2.x`. The two artifacts have separate version lines, so a lower `flowvault` version number does not mean it is older or behind — it is a first release, not a downgrade. Upgrade each artifact on its own.
+
+[](https://github.com/skyflowapi/skyflow-java/actions)
+[](https://github.com/skyflowapi/skyflow-java/blob/main/LICENSE)
+
+# Table of Contents
+
+- [Table of Contents](#table-of-contents)
+- [Overview](#overview)
+- [Install](#install)
+ - [Requirements](#requirements)
+ - [Configuration](#configuration)
+- [Quickstart](#quickstart)
+- [Authenticate](#authenticate)
+ - [Credential types](#credential-types)
+ - [Where credentials can be set](#where-credentials-can-be-set)
+ - [Generate a bearer token](#generate-a-bearer-token)
+ - [Context-aware and scoped tokens](#context-aware-and-scoped-tokens)
+- [Initialize the client](#initialize-the-client)
+ - [VaultConfig reference](#vaultconfig-reference)
+ - [Skyflow.builder() reference](#skyflowbuilder-reference)
+ - [Timeouts and retries](#timeouts-and-retries)
+ - [Logging](#logging)
+- [VaultController — Bulk operations](#vaultcontroller--bulk-operations)
+ - [Batching and concurrency](#batching-and-concurrency)
+- [Bulk Insert](#bulk-insert)
+- [Bulk Tokenize](#bulk-tokenize)
+- [Bulk Detokenize](#bulk-detokenize)
+- [Bulk Delete Tokens](#bulk-delete-tokens)
+- [Custom Request Headers](#custom-request-headers)
+- [Error Handling](#error-handling)
+ - [Two layers of errors](#two-layers-of-errors)
+ - [Per-record success and failure](#per-record-success-and-failure)
+ - [Catching SkyflowException](#catching-skyflowexception)
+ - [SkyflowException properties](#skyflowexception-properties)
+ - [Retrying the failed records](#retrying-the-failed-records)
+
+# Overview
+
+- Authenticate using a Skyflow service account, an API key, or a bearer token — see [Authenticate](#authenticate).
+- Perform bulk Vault API operations — insert, tokenize, detokenize, and delete tokens — each with a synchronous and an async variant, built for high-throughput Flow DB workloads.
+- **Per-record reporting, not all-or-nothing.** A bulk call succeeds as a call even when individual records fail; every response reports a summary plus the outcome of each individual record or token. See [Error Handling](#error-handling).
+
+# Install
+
+## Requirements
+
+- Java 8 and above
+
+## Configuration
+
+### Gradle users
+
+```
+implementation 'com.skyflow:skyflow-flowvault-java:1.0.0'
+```
+
+### Maven users
+
+```xml
+
+ com.skyflow
+ skyflow-flowvault-java
+ 1.0.0
+
+```
+
+# Quickstart
+
+```java
+import com.skyflow.Skyflow;
+import com.skyflow.config.Credentials;
+import com.skyflow.config.VaultConfig;
+import com.skyflow.enums.Env;
+import com.skyflow.vault.controller.VaultController;
+
+Credentials credentials = new Credentials();
+credentials.setApiKey(""); // or setToken / setCredentialsString / setPath
+
+VaultConfig vaultConfig = new VaultConfig();
+vaultConfig.setVaultId("");
+vaultConfig.setClusterId(""); // part of the vault URL, e.g. https://{clusterId}.vault.skyflowapis.com
+vaultConfig.setEnv(Env.PROD);
+vaultConfig.setCredentials(credentials);
+
+Skyflow skyflowClient = Skyflow.builder()
+ .addVaultConfig(vaultConfig)
+ .build();
+
+// Returns the controller for the first configured vault
+VaultController vault = skyflowClient.vault();
+```
+
+`flowvault`'s `vault()` takes no arguments — it always resolves to the first vault added to the builder. Use one client per vault if you need to talk to more than one.
+
+# Authenticate
+
+Requests are authorized with Skyflow credentials that you attach to a `Credentials` object. `Credentials` comes from the shared `common` module, so it is the same class `skyvault` uses.
+
+## Credential types
+
+Set exactly one of the following on a `Credentials` instance. If you set more than one, **the last one set wins**.
+
+| Credential | Setter | What it is |
+|---|---|---|
+| API key | `setApiKey(String)` | A long-lived key that authenticates and authorizes requests to the API. Simplest option. |
+| Bearer token | `setToken(String)` | A short-lived access token, typically generated from service account credentials. See [Generate a bearer token](#generate-a-bearer-token). |
+| Credentials file path | `setPath(String)` | Filesystem path to a service account `credentials.json`. The SDK generates and refreshes bearer tokens from it. |
+| Credentials string | `setCredentialsString(String)` | The contents of a service account `credentials.json` as a JSON string — use this when the credentials come from a secret store rather than a file. |
+
+Two optional modifiers apply when the SDK is generating tokens for you (that is, with `setPath` or `setCredentialsString`):
+
+| Setter | Description |
+|---|---|
+| `setRoles(ArrayList)` | Restrict the generated token to specific role IDs (a scoped token). |
+| `setContext(String)` / `setContext(Map)` | Attach context to the generated token for context-aware authorization. |
+
+```java
+// API key
+Credentials apiKeyCredentials = new Credentials();
+apiKeyCredentials.setApiKey("");
+
+// Bearer token you generated yourself
+Credentials tokenCredentials = new Credentials();
+tokenCredentials.setToken("");
+
+// Service account credentials file — the SDK handles token generation and refresh
+Credentials fileCredentials = new Credentials();
+fileCredentials.setPath("");
+
+// Service account credentials as a JSON string
+Credentials stringCredentials = new Credentials();
+stringCredentials.setCredentialsString("");
+```
+
+## Where credentials can be set
+
+Credentials resolve **most specific first**:
+
+1. **Per-vault** — `vaultConfig.setCredentials(credentials)`. Wins for that vault.
+2. **Client-wide** — `Skyflow.builder().addSkyflowCredentials(credentials)`. Used by any vault that has none of its own.
+3. **Environment** — if neither is provided, the SDK reads the `SKYFLOW_CREDENTIALS` environment variable.
+
+If none of the three yields credentials, the call fails with a `SkyflowException`.
+
+## Generate a bearer token
+
+If you would rather manage tokens yourself, `common` ships the same `BearerToken` utility as `skyvault`:
+
+```java
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.serviceaccount.util.BearerToken;
+
+import java.io.File;
+
+BearerToken token = BearerToken.builder()
+ .setCredentials(new File("")) // or setCredentials(credentialsJsonString)
+ .build();
+
+String bearerToken = token.getBearerToken(); // cached and regenerated only when expired
+
+Credentials credentials = new Credentials();
+credentials.setToken(bearerToken);
+```
+
+`getBearerToken()` caches the token and only mints a new one once the current one has expired, so it is safe to call per request.
+
+## Context-aware and scoped tokens
+
+`BearerToken.builder()` also accepts `setCtx(String | Map)` for context-aware authorization and `setRoles(ArrayList)` for scoped tokens. Signed data tokens are available through `com.skyflow.serviceaccount.util.SignedDataTokens`. These utilities are identical to skyvault's — see [Authenticate with bearer tokens](../skyvault/README.md#authenticate-with-bearer-tokens) for worked examples of every variant.
+
+# Initialize the client
+
+`Skyflow` is the client. Build it once, keep it for the lifetime of your application, and get a `VaultController` from it with `vault()`.
+
+```java
+import com.skyflow.Skyflow;
+import com.skyflow.config.Credentials;
+import com.skyflow.config.VaultConfig;
+import com.skyflow.enums.Env;
+import com.skyflow.enums.LogLevel;
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
+
+public class InitFlowVaultClient {
+ public static void main(String[] args) throws SkyflowException {
+ // Step 1: Credentials — exactly one credential type
+ Credentials credentials = new Credentials();
+ credentials.setPath("");
+
+ // Step 2: Vault configuration
+ VaultConfig vaultConfig = new VaultConfig();
+ vaultConfig.setVaultId("");
+ vaultConfig.setClusterId("");
+ vaultConfig.setEnv(Env.PROD); // DEV, STAGE, SANDBOX, or PROD (default)
+ vaultConfig.setCredentials(credentials);
+
+ // Optional: vault-level HTTP overrides
+ vaultConfig.setTimeout(120); // overall call timeout, in seconds
+ vaultConfig.setMaxRetries(2); // retries after the first failure
+
+ // Step 3: Build the client
+ Skyflow skyflowClient = Skyflow.builder()
+ .setLogLevel(LogLevel.INFO) // default is ERROR
+ .addVaultConfig(vaultConfig)
+ .build();
+
+ // Step 4: Get the controller and issue bulk calls
+ VaultController vault = skyflowClient.vault();
+ }
+}
+```
+
+## VaultConfig reference
+
+| Setter | Type | Description |
+|---|---|---|
+| `setVaultId(String)` | required | The vault's ID. |
+| `setClusterId(String)` | required | The cluster portion of the vault URL — `https://{clusterId}.vault.skyflowapis.com`. |
+| `setEnv(Env)` | optional | `Env.DEV`, `Env.STAGE`, `Env.SANDBOX`, or `Env.PROD`. Defaults to `PROD`; passing `null` also resolves to `PROD`. |
+| `setCredentials(Credentials)` | optional | Credentials for this vault. Falls back to client-wide credentials, then `SKYFLOW_CREDENTIALS`. |
+| `setVaultUrl(String)` | optional | Full vault URL, when it cannot be derived from `clusterId` and `env`. |
+| `setTimeout(Integer)` | optional | Overall call timeout in seconds, including retries. |
+| `setConnectTimeout(Integer)` | optional | Per-attempt connection timeout, in seconds. |
+| `setReadTimeout(Integer)` | optional | Per-attempt response-read timeout, in seconds. |
+| `setWriteTimeout(Integer)` | optional | Per-attempt request-write timeout, in seconds. |
+| `setMaxRetries(Integer)` | optional | Retry attempts after the first failure. |
+| `setInitialRetryDelayMillis(Long)` | optional | Backoff before the first retry, in milliseconds. |
+| `setMaxRetryDelayMillis(Long)` | optional | Ceiling the exponential backoff grows to, in milliseconds. |
+
+## Skyflow.builder() reference
+
+| Method | Description |
+|---|---|
+| `addVaultConfig(VaultConfig)` | Register a vault. The first one registered is what `vault()` returns. |
+| `updateVaultConfig(VaultConfig)` | Update a registered vault in place. `null` fields mean "leave as is". |
+| `removeVaultConfig(String vaultId)` | Unregister a vault. |
+| `addSkyflowCredentials(Credentials)` | Client-wide credentials for vaults that don't set their own. |
+| `setLogLevel(LogLevel)` | `DEBUG`, `INFO`, `WARN`, `ERROR` (default), or `OFF`. |
+| `timeout(int)` / `connectTimeout(int)` / `readTimeout(int)` / `writeTimeout(int)` | Client-wide HTTP timeouts, in seconds. |
+| `maxRetries(int)` / `initialRetryDelayMillis(long)` / `maxRetryDelayMillis(long)` | Client-wide retry policy. |
+| `build()` | Produce the `Skyflow` client. |
+
+Every method throws `SkyflowException` on validation errors and returns the builder for chaining.
+
+## Timeouts and retries
+
+Each HTTP setting resolves **most specific first**: the value on `VaultConfig`, else the client-wide value on `Skyflow.builder()`, else the SDK default. Only `null` means "inherit" — an explicit `0` is a real value and overrides the level below it.
+
+| Setting | SDK default |
+|---|---|
+| `timeout` (overall call, incl. retries) | 60 s |
+| `connectTimeout` / `readTimeout` / `writeTimeout` (per attempt) | 10 s (underlying HTTP client default) |
+| `maxRetries` | `0` — retries are **opt-in**, so non-idempotent bulk writes are never replayed silently |
+| `initialRetryDelayMillis` | 500 ms |
+| `maxRetryDelayMillis` | 2000 ms |
+
+```java
+// Client-wide policy, overridden for one vault
+VaultConfig vaultConfig = new VaultConfig();
+vaultConfig.setVaultId("");
+vaultConfig.setClusterId("");
+vaultConfig.setCredentials(credentials);
+vaultConfig.setTimeout(300); // this vault gets 300s...
+
+Skyflow skyflowClient = Skyflow.builder()
+ .timeout(60) // ...instead of the client-wide 60s
+ .maxRetries(3) // this vault inherits 3 retries
+ .initialRetryDelayMillis(500L)
+ .maxRetryDelayMillis(4000L)
+ .addVaultConfig(vaultConfig)
+ .build();
+```
+
+## Logging
+
+The SDK logs through `java.util.logging` at `LogLevel.ERROR` by default. Levels rank `DEBUG` < `INFO` < `WARN` < `ERROR` < `OFF`; setting a level prints that level and everything above it. Change it with `Skyflow.builder().setLogLevel(LogLevel.DEBUG)`.
+
+# VaultController — Bulk operations
+
+`VaultController` is returned by `skyflowClient.vault()`. `flowvault` exposes these bulk vault operations:
+
+| Method | Parameters | Returns | Description |
+|--------|-----------|---------|-------------|
+| `bulkInsert(BulkInsertRequest)` | `BulkInsertRequest`, optional `BulkInsertOptions` | `BulkInsertResponse` | Insert many records, optionally across multiple tables, in one call |
+| `bulkInsertAsync(BulkInsertRequest)` | same | `CompletableFuture` | Async variant of `bulkInsert` |
+| `bulkTokenize(BulkTokenizeRequest)` | `BulkTokenizeRequest`, optional `BulkTokenizeOptions` | `BulkTokenizeResponse` | Tokenize many values, each against one or more named token groups |
+| `bulkTokenizeAsync(BulkTokenizeRequest)` | same | `CompletableFuture` | Async variant of `bulkTokenize` |
+| `bulkDetokenize(BulkDetokenizeRequest)` | `BulkDetokenizeRequest`, optional `BulkDetokenizeOptions` | `BulkDetokenizeResponse` | Detokenize many tokens, optionally with a redaction override per token group |
+| `bulkDetokenizeAsync(BulkDetokenizeRequest)` | same | `CompletableFuture` | Async variant of `bulkDetokenize` |
+| `bulkDeleteTokens(BulkDeleteTokensRequest)` | `BulkDeleteTokensRequest`, optional `BulkDeleteTokensOptions` | `BulkDeleteTokensResponse` | Delete many tokens in one call |
+| `bulkDeleteTokensAsync(BulkDeleteTokensRequest)` | same | `CompletableFuture` | Async variant of `bulkDeleteTokens` |
+
+Each method also accepts an optional options object (`BulkInsertOptions`, `BulkTokenizeOptions`, `BulkDetokenizeOptions`, `BulkDeleteTokensOptions`) — see [Custom Request Headers](#custom-request-headers).
+
+A single bulk call accepts at most **10,000** records or tokens; anything larger is rejected up front with a `SkyflowException`. Under that ceiling the SDK splits the payload into batches and sends them concurrently, which is why errors from one call can carry different `requestId` values.
+
+Every bulk response has the same two-part shape:
+
+- a **summary** — totals for the call (e.g. `totalRecords` / `totalInserted` / `totalFailed` for insert)
+- a **records** list — one entry per submitted record or token, in input order, each carrying its own `index`, `httpCode`, and `error`
+
+That per-record shape is the point of these APIs; see [Error Handling](#error-handling) for the full model.
+
+## Batching and concurrency
+
+Batch size and concurrency are configured **per operation** through environment variables — there is no builder or options API for them. Each value is read from the process environment first, then from a `.env` file in the working directory.
+
+| Operation | Batch size variable | Default | Max | Concurrency variable | Default | Max |
+|-----------|--------------------|---------|-----|---------------------|---------|-----|
+| Bulk insert | `INSERT_BATCH_SIZE` | 50 | 1000 | `INSERT_CONCURRENCY_LIMIT` | 1 | 10 |
+| Bulk tokenize | `TOKENIZE_BATCH_SIZE` | 50 | 1000 | `TOKENIZE_CONCURRENCY_LIMIT` | 1 | 10 |
+| Bulk detokenize | `DETOKENIZE_BATCH_SIZE` | 50 | 1000 | `DETOKENIZE_CONCURRENCY_LIMIT` | 1 | 10 |
+| Bulk delete tokens | `DELETE_TOKENS_BATCH_SIZE` | 50 | 1000 | `DELETE_TOKENS_CONCURRENCY_LIMIT` | 1 | 10 |
+
+Concurrency defaults to **1**, so batches are sent one after another unless you raise the limit.
+
+How each value is resolved:
+
+- **Batch size** — `min(yourValue, max)`. Above the max, the SDK logs a warning and uses the max. Zero, negative, or non-numeric values log a warning and fall back to the default.
+- **Concurrency** — `min(yourValue, max, batchCount)`, where `batchCount = ceil(itemCount / batchSize)`. Concurrency never exceeds the number of batches there are to run. Same warning-and-fallback behaviour for invalid values.
+
+Those warnings are emitted at `WARN`, which the default `ERROR` level hides — set `LogLevel.WARN` or below to see them (see [Logging](#logging)).
+
+For example, 500 records with `INSERT_BATCH_SIZE=100` and `INSERT_CONCURRENCY_LIMIT=10` produces 5 batches, all 5 in flight at once — the concurrency is capped to 5, not 10.
+
+```dotenv
+# .env
+INSERT_BATCH_SIZE=100
+INSERT_CONCURRENCY_LIMIT=5
+```
+
+The 10,000-item ceiling per bulk call is a separate, fixed limit and is not configurable.
+
+# Bulk Insert
+
+Insert many records — even across different tables — in a single call. Each record is a `BulkInsertRequestRecord` with its own `data` and, optionally, its own `tableName` and `upsert`.
+
+**Note:**
+
+- `tableName` must be specified at exactly one level: either on the request (`BulkInsertRequest.builder().tableName(...)`) or on **every** record (`BulkInsertRequestRecord.builder().tableName(...)`) — not both, and not neither.
+- `upsert` is optional, but wherever you supply it, it must sit at the same level as `tableName`. Request-level `tableName` pairs with request-level `upsert`; record-level `tableName` pairs with per-record `upsert`.
+- `UpsertOptions` requires `uniqueColumns`. `updateType` accepts `"UPDATE"` (the default) or `"REPLACE"`.
+
+### Construct a bulk insert request
+
+```java
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.data.BulkInsertRequest;
+import com.skyflow.vault.data.BulkInsertRequestRecord;
+import com.skyflow.vault.data.BulkInsertResponse;
+import com.skyflow.vault.data.InsertRequestRecord;
+import com.skyflow.vault.data.UpsertOptions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BulkInsertExample {
+ public static void main(String[] args) throws SkyflowException {
+ // Step 1: Build each record. Here tableName lives on the records, so each one carries it.
+ Map record1Data = new HashMap<>();
+ record1Data.put("card_number", "4111111111111111");
+ record1Data.put("cardholder_name", "john doe");
+
+ BulkInsertRequestRecord record1 = BulkInsertRequestRecord.builder()
+ .tableName("table1")
+ .data(record1Data)
+ .build();
+
+ Map record2Data = new HashMap<>();
+ record2Data.put("email", "jane.doe@example.com");
+
+ BulkInsertRequestRecord record2 = BulkInsertRequestRecord.builder()
+ .tableName("table2")
+ .data(record2Data)
+ // upsert sits at the record level here, matching where tableName sits
+ .upsert(UpsertOptions.builder()
+ .uniqueColumns(Arrays.asList("email"))
+ .updateType("UPDATE")
+ .build())
+ .build();
+
+ List records = new ArrayList<>();
+ records.add(record1);
+ records.add(record2);
+
+ // Step 2: Build the BulkInsertRequest
+ BulkInsertRequest insertRequest = BulkInsertRequest.builder()
+ .records(records)
+ .build();
+
+ // Step 3: Perform the bulk insert
+ BulkInsertResponse insertResponse = vault.bulkInsert(insertRequest);
+ System.out.println(insertResponse);
+ }
+}
+```
+
+To put the table name on the request instead, drop `tableName` from every record and build the request as:
+
+```java
+BulkInsertRequest insertRequest = BulkInsertRequest.builder()
+ .tableName("table1")
+ .upsert(UpsertOptions.builder().uniqueColumns(Arrays.asList("email")).build())
+ .records(records)
+ .build();
+```
+
+### Async bulk insert
+
+```java
+import java.util.concurrent.CompletableFuture;
+
+CompletableFuture future = vault.bulkInsertAsync(insertRequest);
+future.thenAccept(response -> System.out.println(response));
+```
+
+Sample response:
+
+```json
+{
+ "summary": { "totalRecords": 2, "totalInserted": 1, "totalFailed": 1 },
+ "records": [
+ {
+ "index": 0,
+ "requestId": null,
+ "tableName": "table1",
+ "skyflowId": "9fac9201-7b8a-4446-93f8-5244e1213bd1",
+ "fields": { "card_number": "5484-7829-1702-9110", "cardholder_name": "b2308e2a-c1f5-469b-97b7-1f193159399b" },
+ "hashedData": null,
+ "httpCode": 200,
+ "error": null
+ },
+ {
+ "index": 1,
+ "requestId": "a1b2c3d4-...",
+ "tableName": "table2",
+ "skyflowId": null,
+ "fields": null,
+ "hashedData": null,
+ "httpCode": 400,
+ "error": "Insert failed. Column email is invalid."
+ }
+ ]
+}
+```
+
+Accessors: `insertResponse.getSummary()`, `insertResponse.getRecords()`, and on each record `getIndex()`, `getTableName()`, `getSkyflowId()`, `getFields()`, `getHashedData()`, `getHttpCode()`, `getError()`, `getRequestId()`.
+
+Use `insertResponse.getRecordsToRetry()` to get back only the `BulkInsertRequestRecord`s worth resubmitting — see [Retrying the failed records](#retrying-the-failed-records).
+
+# Bulk Tokenize
+
+Tokenize many values in one call. Each value can be tokenized against one or more named token groups.
+
+### Construct a bulk tokenize request
+
+```java
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.data.BulkTokenizeRequest;
+import com.skyflow.vault.data.BulkTokenizeRequestRecord;
+import com.skyflow.vault.data.BulkTokenizeResponse;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class BulkTokenizeExample {
+ public static void main(String[] args) throws SkyflowException {
+ BulkTokenizeRequestRecord record1 = BulkTokenizeRequestRecord.builder()
+ .value("4111111111111111")
+ .tokenGroupNames(Arrays.asList("card_number_cg"))
+ .build();
+
+ BulkTokenizeRequestRecord record2 = BulkTokenizeRequestRecord.builder()
+ .value("john.doe@example.com")
+ .tokenGroupNames(Arrays.asList("email_cg"))
+ .build();
+
+ List records = new ArrayList<>();
+ records.add(record1);
+ records.add(record2);
+
+ BulkTokenizeRequest tokenizeRequest = BulkTokenizeRequest.builder()
+ .records(records)
+ .build();
+
+ BulkTokenizeResponse tokenizeResponse = vault.bulkTokenize(tokenizeRequest);
+ System.out.println(tokenizeResponse);
+ }
+}
+```
+
+`BulkTokenizeRequestRecord.builder()` also accepts `token(Object)` to supply your own token for the value instead of having the vault generate one.
+
+### Async bulk tokenize
+
+```java
+CompletableFuture future = vault.bulkTokenizeAsync(tokenizeRequest);
+```
+
+Sample response:
+
+```json
+{
+ "summary": { "totalTokens": 2, "totalTokenized": 1, "totalPartial": 0, "totalFailed": 1 },
+ "records": [
+ {
+ "index": 0,
+ "value": "4111111111111111",
+ "tokens": [
+ { "tokenGroupName": "card_number_cg", "token": "5479-4229-4622-1393", "httpCode": 200, "error": null, "requestId": null }
+ ]
+ },
+ {
+ "index": 1,
+ "value": "john.doe@example.com",
+ "tokens": [
+ { "tokenGroupName": "email_cg", "token": null, "httpCode": 400, "error": "Token group email_cg not found.", "requestId": "a1b2c3d4-..." }
+ ]
+ }
+ ]
+}
+```
+
+Tokenize reports at **two** levels: one entry per input value in `records`, and inside each of those, one entry per requested token group in `tokens`. Because a single value can map to several token groups, the summary distinguishes fully tokenized values (`totalTokenized`), partially tokenized values where some groups succeeded and others failed (`totalPartial`), and fully failed values (`totalFailed`). The three always add up to `totalTokens`, which counts input values, not tokens produced.
+
+# Bulk Detokenize
+
+Detokenize many tokens in one call, optionally overriding the redaction applied per token group via `tokenGroupRedactions`.
+
+### Construct a bulk detokenize request
+
+```java
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.data.BulkDetokenizeRequest;
+import com.skyflow.vault.data.BulkDetokenizeResponse;
+import com.skyflow.vault.data.TokenGroupRedactions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class BulkDetokenizeExample {
+ public static void main(String[] args) throws SkyflowException {
+ List tokens = new ArrayList<>(Arrays.asList(
+ "5479-4229-4622-1393",
+ "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
+ ));
+
+ // redaction is a free-form string understood by the vault (e.g. "PLAIN_TEXT",
+ // "MASKED", "REDACTED", "DEFAULT" — the same redaction types as skyvault's RedactionType enum)
+ TokenGroupRedactions redaction = TokenGroupRedactions.builder()
+ .tokenGroupName("card_number_cg")
+ .redaction("MASKED")
+ .build();
+
+ BulkDetokenizeRequest detokenizeRequest = BulkDetokenizeRequest.builder()
+ .tokens(tokens)
+ .tokenGroupRedactions(Arrays.asList(redaction))
+ .build();
+
+ BulkDetokenizeResponse detokenizeResponse = vault.bulkDetokenize(detokenizeRequest);
+ System.out.println(detokenizeResponse);
+ }
+}
+```
+
+### Async bulk detokenize
+
+```java
+CompletableFuture future = vault.bulkDetokenizeAsync(detokenizeRequest);
+```
+
+Sample response:
+
+```json
+{
+ "summary": { "totalTokens": 2, "totalDetokenized": 1, "totalFailed": 1 },
+ "records": [
+ {
+ "index": 0,
+ "requestId": null,
+ "value": "4111111111111111",
+ "tokenGroupName": "card_number_cg",
+ "metadata": {},
+ "httpCode": 200,
+ "token": "5479-4229-4622-1393",
+ "error": null
+ },
+ {
+ "index": 1,
+ "requestId": "a1b2c3d4-...",
+ "value": null,
+ "tokenGroupName": null,
+ "metadata": null,
+ "httpCode": 404,
+ "token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "error": "Token Not Found"
+ }
+ ]
+}
+```
+
+Use `detokenizeResponse.getTokensToRetry()` to get back only the tokens worth resubmitting.
+
+# Bulk Delete Tokens
+
+Delete many tokens in one call.
+
+### Construct a bulk delete tokens request
+
+```java
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.data.BulkDeleteTokensRequest;
+import com.skyflow.vault.data.BulkDeleteTokensResponse;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class BulkDeleteTokensExample {
+ public static void main(String[] args) throws SkyflowException {
+ List tokens = new ArrayList<>(Arrays.asList(
+ "5479-4229-4622-1393",
+ "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
+ ));
+
+ BulkDeleteTokensRequest deleteTokensRequest = BulkDeleteTokensRequest.builder()
+ .tokens(tokens)
+ .build();
+
+ BulkDeleteTokensResponse deleteTokensResponse = vault.bulkDeleteTokens(deleteTokensRequest);
+ System.out.println(deleteTokensResponse);
+ }
+}
+```
+
+### Async bulk delete tokens
+
+```java
+CompletableFuture future = vault.bulkDeleteTokensAsync(deleteTokensRequest);
+```
+
+Sample response:
+
+```json
+{
+ "summary": { "totalTokens": 2, "totalDeleted": 2, "totalFailed": 0 },
+ "records": [
+ { "index": 0, "token": "5479-4229-4622-1393", "httpCode": 200, "error": null, "requestId": null },
+ { "index": 1, "token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "httpCode": 200, "error": null, "requestId": null }
+ ]
+}
+```
+
+Use `deleteTokensResponse.getTokensToRetry()` to get back only the tokens worth resubmitting.
+
+# Custom Request Headers
+
+To include custom HTTP headers on an outgoing bulk request, pass a `RequestInterceptor` via that operation's options object. The headers available are defined by the `CustomHeaderKey` enum:
+
+| `CustomHeaderKey` | HTTP header name |
+|---|---|
+| `SkyflowAccountId` | `x-skyflow-account-id` |
+| `SkyflowAccountName` | `x-skyflow-account-name` |
+| `RequestIdHeader` | `x-request-id` |
+
+```java
+import com.skyflow.enums.CustomHeaderKey;
+import com.skyflow.vault.data.BulkInsertOptions;
+
+BulkInsertOptions options = BulkInsertOptions.builder()
+ .interceptor(context -> context.addHeader(CustomHeaderKey.RequestIdHeader, ""))
+ .build();
+
+BulkInsertResponse insertResponse = vault.bulkInsert(insertRequest, options);
+```
+
+The interceptor runs **once per batch**, not once per bulk call — so a value generated inside it (a fresh request id, say) differs between the batches a single bulk call is split into.
+
+The same pattern applies to every bulk operation, via its corresponding options class:
+
+| Operation | Options class |
+|---|---|
+| `bulkInsert` / `bulkInsertAsync` | `BulkInsertOptions` |
+| `bulkTokenize` / `bulkTokenizeAsync` | `BulkTokenizeOptions` |
+| `bulkDetokenize` / `bulkDetokenizeAsync` | `BulkDetokenizeOptions` |
+| `bulkDeleteTokens` / `bulkDeleteTokensAsync` | `BulkDeleteTokensOptions` |
+
+# Error Handling
+
+## Two layers of errors
+
+This is the mental model to hold for every bulk operation:
+
+| Layer | What it covers | How you see it |
+|---|---|---|
+| **Request-level** | The call could not be made or the whole call failed: invalid request shape, missing credentials, auth failure, payload over the 10,000-item limit. | A thrown `SkyflowException`. No results at all. |
+| **Record-level** | The call succeeded, but individual records or tokens inside it did not. | A returned response. **Nothing is thrown.** Each entry in `getRecords()` reports its own `httpCode` and `error`. |
+
+The second layer is what distinguishes `flowvault` from an all-or-nothing API: **a bulk call that returns normally can still contain failures, and a call where every single record failed also returns normally rather than throwing.** Checking only for a thrown exception will silently miss failed records — always read the summary and the per-record results.
+
+## Per-record success and failure
+
+Every bulk response exposes `getSummary()` and `getRecords()`. The records list has one entry per submitted item, in the order you submitted it, and each entry carries:
+
+| Field | Present on | Meaning |
+|---|---|---|
+| `getIndex()` | always | Position of this item in the payload you submitted — use it to line results back up with your input. |
+| `getHttpCode()` | always | Per-item status. `2xx` for success; `4xx`/`5xx` for failure. |
+| `getError()` | failures only | Error message for this item. `null` means this item succeeded. |
+| `getRequestId()` | failures only | The `x-request-id` of the batch this item was in — quote it in support escalations. Items from the same batch share one id. |
+
+The success payload sits alongside those fields on the same object: `getSkyflowId()`/`getFields()` for insert, `getValue()`/`getTokenGroupName()`/`getMetadata()` for detokenize, `getTokens()` for tokenize, `getToken()` for delete.
+
+Summaries per operation:
+
+| Response | Summary type | Fields |
+|---|---|---|
+| `BulkInsertResponse` | `BulkSummary` | `totalRecords`, `totalInserted`, `totalFailed` |
+| `BulkTokenizeResponse` | `TokenizeSummary` | `totalTokens`, `totalTokenized`, `totalPartial`, `totalFailed` |
+| `BulkDetokenizeResponse` | `DetokenizeSummary` | `totalTokens`, `totalDetokenized`, `totalFailed` |
+| `BulkDeleteTokensResponse` | `DeleteTokensSummary` | `totalTokens`, `totalDeleted`, `totalFailed` |
+
+The idiomatic way to consume a bulk response:
+
+```java
+BulkInsertResponse response = vault.bulkInsert(insertRequest);
+
+System.out.println("inserted " + response.getSummary().getTotalInserted()
+ + " of " + response.getSummary().getTotalRecords());
+
+for (BulkInsertResponseRecord record : response.getRecords()) {
+ if (record.getError() == null) {
+ System.out.println("row " + record.getIndex() + " -> " + record.getSkyflowId());
+ } else {
+ System.err.println("row " + record.getIndex() + " failed ["
+ + record.getHttpCode() + "] " + record.getError()
+ + " (requestId " + record.getRequestId() + ")");
+ }
+}
+```
+
+For tokenize, the check is one level deeper, because a single value can partially succeed:
+
+```java
+for (BulkTokenizeResponseRecord record : tokenizeResponse.getRecords()) {
+ for (TokenizeResponseToken token : record.getTokens()) {
+ if (token.getError() == null) {
+ System.out.println(record.getIndex() + "/" + token.getTokenGroupName()
+ + " -> " + token.getToken());
+ } else {
+ System.err.println(record.getIndex() + "/" + token.getTokenGroupName()
+ + " failed [" + token.getHttpCode() + "] " + token.getError());
+ }
+ }
+}
+```
+
+## Catching SkyflowException
+
+`SkyflowException` covers the request-level layer only — client-side validation errors and whole-call API errors. It comes from `common`, so it is the same exception type `skyvault` throws.
+
+```java
+import com.skyflow.errors.SkyflowException;
+
+try {
+ BulkInsertResponse response = vault.bulkInsert(insertRequest);
+ // reaching here means the CALL succeeded — individual records may still have failed
+} catch (SkyflowException e) {
+ System.err.println("Skyflow error:");
+ System.err.println(" HTTP code : " + e.getHttpCode());
+ System.err.println(" Message : " + e.getMessage());
+ System.err.println(" Request ID: " + e.getRequestId());
+ System.err.println(" Details : " + e.getDetails());
+} catch (Exception e) {
+ System.err.println("Unexpected error: " + e.getMessage());
+}
+```
+
+For the async variants, the same exception arrives wrapped in a `CompletionException`:
+
+```java
+vault.bulkInsertAsync(insertRequest)
+ .thenAccept(response -> System.out.println(response))
+ .exceptionally(throwable -> {
+ System.err.println("bulk insert failed: " + throwable.getCause().getMessage());
+ return null;
+ });
+```
+
+## SkyflowException properties
+
+| Property | Method | Description |
+|---|---|---|
+| HTTP status code | `getHttpCode()` | Integer status code (e.g. `400`, `404`, `500`). |
+| Message | `getMessage()` | Human-readable description of the error. |
+| HTTP status string | `getHttpStatus()` | Status string from the server (e.g. `"BAD_REQUEST"`). |
+| gRPC code | `getGrpcCode()` | gRPC status code from the server. |
+| Request ID | `getRequestId()` | The `x-request-id` header — useful for support escalations. |
+| Details | `getDetails()` | `JsonArray` of additional error context from the server. Empty array for validation errors, `null` if the server response omitted the field. |
+
+**Validation errors** (table name at the wrong level, empty token list, payload over 10,000 items, and similar) are thrown before any network call:
+
+- `httpCode` is always `400`
+- `requestId` and `grpcCode` are `null`
+- `details` is an empty array
+
+**API errors** are returned by the Skyflow server and have all fields populated from the response body and headers.
+
+## Retrying the failed records
+
+Because failures are reported per record, a partial failure can be retried without resubmitting the whole payload. Each response exposes a retry helper that filters its records down to the ones worth resending — **server-side failures (HTTP 500–599), excluding 529**, which is a permanent capacity-limit code:
+
+| Response | Helper | Returns |
+|---|---|---|
+| `BulkInsertResponse` | `getRecordsToRetry()` | `List` — your original record objects, ready to resubmit |
+| `BulkTokenizeResponse` | `getRecordsToRetry()` | `List` — values with at least one retryable token-group failure |
+| `BulkDetokenizeResponse` | `getTokensToRetry()` | `List` — the tokens to resubmit |
+| `BulkDeleteTokensResponse` | `getTokensToRetry()` | `List` — the tokens to resubmit |
+
+```java
+BulkInsertResponse response = vault.bulkInsert(insertRequest);
+
+List retryable = response.getRecordsToRetry();
+if (!retryable.isEmpty()) {
+ BulkInsertResponse retryResponse = vault.bulkInsert(
+ BulkInsertRequest.builder()
+ .tableName("table1")
+ .records(new ArrayList<>(retryable))
+ .build());
+}
+```
+
+Client-side (`4xx`) failures are deliberately excluded — those need a fix to the data, not a retry. This is separate from the transport-level `maxRetries` setting in [Timeouts and retries](#timeouts-and-retries), which retries whole HTTP attempts and is off by default.
diff --git a/flowvault/api-report/skyflow-flowvault-java.baseline.jar b/flowvault/api-report/skyflow-flowvault-java.baseline.jar
new file mode 100644
index 00000000..d98e0eb7
Binary files /dev/null and b/flowvault/api-report/skyflow-flowvault-java.baseline.jar differ
diff --git a/flowvault/pom.xml b/flowvault/pom.xml
index 9b34fa4d..5a8f014f 100644
--- a/flowvault/pom.xml
+++ b/flowvault/pom.xml
@@ -11,7 +11,7 @@
skyflow-flowvault-java
- 3.0.0-beta.13-dev.f72e2218
+ 3.0.0-beta.13-dev.c7311820
jar
${project.groupId}:${project.artifactId}
Skyflow V3 SDK for the Java programming language
@@ -77,6 +77,96 @@
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+
+ shade-for-japicmp
+ package
+
+