Kotlin SDK
Official Kotlin SDK for TruthMark invisible watermarking with coroutine support.
Installation
Gradle (Kotlin DSL)
dependencies {
implementation("com.truthmark:sdk:1.0.0")
}Maven
<dependency>
<groupId>com.truthmark</groupId>
<artifactId>sdk</artifactId>
<version>1.0.0</version>
</dependency>Quick Example
import com.truthmark.sdk.TruthMarkClient
import com.truthmark.sdk.TruthMarkConfig
suspend fun main() {
val client = TruthMarkClient(TruthMarkConfig(
apiKey = "tm_live_your_key",
baseUrl = "https://truthmark-api.onrender.com"
))
// Encode
val result = client.encode("image.png", "Copyright 2025")
println("Download: ${result.downloadUrl}")
println("PSNR: ${String.format("%.1f", result.metadata.psnr)} dB")
// Decode
val decoded = client.decode("watermarked.png")
if (decoded.found) {
println("Message: ${decoded.message}")
println("Confidence: ${String.format("%.1f", decoded.confidence * 100)}%")
}
}API Reference
TruthMarkClient(config: TruthMarkConfig)
Create a new client.
data class TruthMarkConfig(
val apiKey: String,
val baseUrl: String = "https://truthmark-api.onrender.com",
val timeout: Long = 30_000
)suspend fun encode(imagePath: String, message: String): EncodeResult
Embed an invisible watermark. Coroutine-safe.
suspend fun decode(imagePath: String): DecodeResult
Extract watermark. Coroutine-safe.
Error Handling
import com.truthmark.sdk.exceptions.*
try {
val result = client.encode("image.png", "message")
} catch (e: TruthMarkAuthException) {
println("Invalid API key")
} catch (e: TruthMarkRateLimitException) {
println("Rate limited. Retry after: ${e.retryAfter}s")
} catch (e: TruthMarkException) {
println("Error (${e.statusCode}): ${e.message}")
}Advanced Examples
Android ViewModel
class WatermarkViewModel(
private val client: TruthMarkClient
) : ViewModel() {
private val _state = MutableStateFlow<UiState>(UiState.Idle)
val state: StateFlow<UiState> = _state
fun watermarkImage(uri: Uri, message: String) {
viewModelScope.launch {
_state.value = UiState.Loading
try {
val result = client.encode(uri.path!!, message)
_state.value = UiState.Success(result.downloadUrl)
} catch (e: TruthMarkException) {
_state.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
sealed class UiState {
object Idle : UiState()
object Loading : UiState()
data class Success(val url: String) : UiState()
data class Error(val message: String) : UiState()
}
}Ktor Server Route
fun Application.configureRoutes() {
val client = TruthMarkClient(TruthMarkConfig(
apiKey = environment.config.property("truthmark.apiKey").getString()
))
routing {
post("/watermark") {
val multipart = call.receiveMultipart()
var filePath: String? = null
var message: String? = null
multipart.forEachPart { part ->
when (part) {
is PartData.FileItem -> {
val temp = File.createTempFile("upload", ".png")
part.streamProvider().use { input ->
temp.outputStream().use { output -> input.copyTo(output) }
}
filePath = temp.absolutePath
}
is PartData.FormItem -> message = part.value
else -> {}
}
part.dispose()
}
val result = client.encode(filePath!!, message!!)
call.respond(result)
}
}
}Concurrent Batch with Coroutines
suspend fun batchWatermark(directory: String, message: String) =
coroutineScope {
val files = File(directory).listFiles { f ->
f.extension in listOf("png", "jpg", "jpeg")
} ?: return@coroutineScope emptyList()
val semaphore = Semaphore(5)
files.map { file ->
async {
semaphore.withPermit {
try {
val result = client.encode(file.absolutePath, message)
println("✓ ${file.name}: PSNR=${result.metadata.psnr}dB")
result
} catch (e: TruthMarkException) {
println("✗ ${file.name}: ${e.message}")
null
}
}
}
}.awaitAll().filterNotNull()
}Best Practices
Use coroutines for all API calls
All SDK methods are suspend functions — use viewModelScope or lifecycleScope on Android.
Use Semaphore for batch operations
Limit concurrency with kotlinx.coroutines.sync.Semaphore to avoid rate limits.
Don't store keys in strings.xml
Use BuildConfig fields or encrypted SharedPreferences for API keys on Android.