Code examples
Starter snippets for calling the KiTbetter Partner API in commonly used languages.
Create an album ingest (POST /ddex/ingests)
Create a KiTalbum by passing a presigned URL to your DDEX ERN 3.8.2 XML. The ingest response can take up to 30 seconds, so set your client timeout to at least 30 seconds.
\=== "cURL"
```bash
curl -X POST "https://api.kitbetter.com/partner/v1/ddex/ingests" \
-H "X-API-Key: $KIT_API_KEY" \
-H "Content-Type: application/json" \
--max-time 35 \
-d '{
"releaseId": "DIST-2026-00123",
"xmlUrl": "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600"
}'
```
\=== "Python"
```python
import os
import requests
BASE_URL = "https://api.kitbetter.com/partner/v1"
response = requests.post(
f"{BASE_URL}/ddex/ingests",
headers={"X-API-Key": os.environ["KIT_API_KEY"]},
json={
"releaseId": "DIST-2026-00123",
"xmlUrl": "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600",
},
timeout=35, # the ingest response can take up to 30s
)
response.raise_for_status()
ingest = response.json()
print(ingest["ingestId"], ingest["status"])
```
\=== "JavaScript"
```javascript
const BASE_URL = "https://api.kitbetter.com/partner/v1";
const response = await fetch(`${BASE_URL}/ddex/ingests`, {
method: "POST",
headers: {
"X-API-Key": process.env.KIT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
releaseId: "DIST-2026-00123",
xmlUrl: "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600",
}),
signal: AbortSignal.timeout(35_000), // the ingest response can take up to 30s
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const ingest = await response.json();
console.log(ingest.ingestId, ingest.status);
```
\=== "Go"
```go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
func main() {
payload, _ := json.Marshal(map[string]string{
"releaseId": "DIST-2026-00123",
"xmlUrl": "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600",
})
req, _ := http.NewRequest("POST", "https://api.kitbetter.com/partner/v1/ddex/ingests", bytes.NewBuffer(payload))
req.Header.Set("X-API-Key", os.Getenv("KIT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 35 * time.Second} // the ingest response can take up to 30s
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var ingest map[string]any
json.NewDecoder(resp.Body).Decode(&ingest)
fmt.Println(ingest["ingestId"], ingest["status"])
}
```
\=== "Java"
```java
// Java 11+ (java.net.http)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class CreateIngest {
public static void main(String[] args) throws Exception {
String payload = """
{
"releaseId": "DIST-2026-00123",
"xmlUrl": "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600"
}""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.kitbetter.com/partner/v1/ddex/ingests"))
.header("X-API-Key", System.getenv("KIT_API_KEY"))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(35)) // the ingest response can take up to 30s
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}
```
\=== "Kotlin"
```kotlin
// JDK 11+ (java.net.http)
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
fun main() {
val payload = """
{
"releaseId": "DIST-2026-00123",
"xmlUrl": "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600"
}
""".trimIndent()
val client = HttpClient.newHttpClient()
val request = HttpRequest.newBuilder()
.uri(URI.create("https://api.kitbetter.com/partner/v1/ddex/ingests"))
.header("X-API-Key", System.getenv("KIT_API_KEY"))
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(35)) // the ingest response can take up to 30s
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
println("${response.statusCode()} ${response.body()}")
}
```
\=== "C#"
```csharp
using System.Net.Http.Json;
var client = new HttpClient { Timeout = TimeSpan.FromSeconds(35) }; // the ingest response can take up to 30s
client.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("KIT_API_KEY"));
var response = await client.PostAsJsonAsync(
"https://api.kitbetter.com/partner/v1/ddex/ingests",
new
{
releaseId = "DIST-2026-00123",
xmlUrl = "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600",
});
response.EnsureSuccessStatusCode();
var ingest = await response.Content.ReadFromJsonAsync<Dictionary<string, object>>();
Console.WriteLine($"{ingest["ingestId"]} {ingest["status"]}");
```
\=== "PHP"
```php
<?php
$payload = json_encode([
"releaseId" => "DIST-2026-00123",
"xmlUrl" => "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600",
]);
$ch = curl_init("https://api.kitbetter.com/partner/v1/ddex/ingests");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("KIT_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 35, // the ingest response can take up to 30s
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$ingest = json_decode($body, true);
echo $ingest["ingestId"] . " " . $ingest["status"];
```
\=== "Ruby"
```ruby
require "json"
require "net/http"
uri = URI("https://api.kitbetter.com/partner/v1/ddex/ingests")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 35 # the ingest response can take up to 30s
request = Net::HTTP::Post.new(uri)
request["X-API-Key"] = ENV["KIT_API_KEY"]
request["Content-Type"] = "application/json"
request.body = {
releaseId: "DIST-2026-00123",
xmlUrl: "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600"
}.to_json
response = http.request(request)
ingest = JSON.parse(response.body)
puts "#{ingest["ingestId"]} #{ingest["status"]}"
```
\=== "Rust"
```rust
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }, serde_json = "1"
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(35)) // the ingest response can take up to 30s
.build()?;
let ingest: serde_json::Value = client
.post("https://api.kitbetter.com/partner/v1/ddex/ingests")
.header("X-API-Key", env::var("KIT_API_KEY")?)
.json(&serde_json::json!({
"releaseId": "DIST-2026-00123",
"xmlUrl": "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600"
}))
.send()?
.error_for_status()?
.json()?;
println!("{} {}", ingest["ingestId"], ingest["status"]);
Ok(())
}
```
\=== "C++"
```cpp
// Uses cpr as the HTTP client (vcpkg install cpr) — libcurl directly works too
#include <cpr/cpr.h>
#include <cstdlib>
#include <iostream>
int main() {
cpr::Response response = cpr::Post(
cpr::Url{"https://api.kitbetter.com/partner/v1/ddex/ingests"},
cpr::Header{{"X-API-Key", std::getenv("KIT_API_KEY")},
{"Content-Type", "application/json"}},
cpr::Body{R"({
"releaseId": "DIST-2026-00123",
"xmlUrl": "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600"
})"},
cpr::Timeout{35'000}); // the ingest response can take up to 30s
std::cout << response.status_code << " " << response.text << std::endl;
return 0;
}
```
Requests with error handling
Two responses deserve special handling — 409 (duplicate ingest) carries details of the existing ingest in error.details, so you can treat it as a success if it is a retry after a timeout, while 422 (XML processing failed) means no ingest was created, so fix the cause and resend. Logging error.requestId from the response makes it much faster to trace the issue when you contact us.
\=== "cURL"
```bash
# -w: print the status code — for 409/422, check error.code and error.details in the body
curl -s "https://api.kitbetter.com/partner/v1/ddex/ingests" \
-X POST \
-H "X-API-Key: $KIT_API_KEY" \
-H "Content-Type: application/json" \
--max-time 35 \
-d @request.json \
-w "\nHTTP %{http_code}\n"
```
\=== "Python"
```python
import os
import requests
BASE_URL = "https://api.kitbetter.com/partner/v1"
try:
response = requests.post(
f"{BASE_URL}/ddex/ingests",
headers={"X-API-Key": os.environ["KIT_API_KEY"]},
json={
"releaseId": "DIST-2026-00123",
"xmlUrl": "https://your-bucket.s3.amazonaws.com/ern/abc.xml?X-Amz-Expires=3600",
},
timeout=35,
)
response.raise_for_status()
except requests.HTTPError:
error = response.json().get("error", {})
if response.status_code == 409 and error.get("details"):
# releaseId already ingested — if this is a retry after a timeout, treat the existing ingest as a success
print("Existing ingest:", error["details"])
elif response.status_code == 422:
# no ingest was created — fix the cause and resend
print(f"XML processing failed {error.get('code')}: {error.get('message')}")
else:
print(f"Error {error.get('code')}: {error.get('message')} (requestId={error.get('requestId')})")
```
\=== "JavaScript"
```javascript
const response = await fetch(`${BASE_URL}/ddex/ingests`, {
method: "POST",
headers: {
"X-API-Key": process.env.KIT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(35_000),
});
if (!response.ok) {
const { error } = await response.json();
if (response.status === 409 && error.details) {
// releaseId already ingested — if this is a retry after a timeout, treat the existing ingest as a success
console.log("Existing ingest:", error.details);
} else if (response.status === 422) {
// no ingest was created — fix the cause and resend
console.error(`XML processing failed ${error.code}: ${error.message}`);
} else {
console.error(`Error ${error.code}: ${error.message} (requestId=${error.requestId})`);
}
}
```
\=== "Go"
```go
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
var body struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
RequestID string `json:"requestId"`
Details json.RawMessage `json:"details"`
} `json:"error"`
}
json.NewDecoder(resp.Body).Decode(&body)
switch {
case resp.StatusCode == 409 && body.Error.Details != nil:
// releaseId already ingested — if this is a retry after a timeout, treat the existing ingest as a success
fmt.Println("Existing ingest:", string(body.Error.Details))
case resp.StatusCode == 422:
// no ingest was created — fix the cause and resend
fmt.Printf("XML processing failed %s: %s\n", body.Error.Code, body.Error.Message)
default:
fmt.Printf("Error %s: %s (requestId=%s)\n", body.Error.Code, body.Error.Message, body.Error.RequestID)
}
}
```
\=== "Java"
```java
// Parse the response body with Jackson, Gson, or similar — the snippet below only shows the status code branching
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
switch (response.statusCode()) {
case 201 -> System.out.println("Ingest created: " + response.body());
// releaseId already ingested — if this is a retry after a timeout, treat the existing ingest in error.details as a success
case 409 -> System.out.println("Duplicate ingest: " + response.body());
// no ingest was created — fix the cause and resend
case 422 -> System.out.println("XML processing failed: " + response.body());
default -> System.err.println("Error " + response.statusCode() + ": " + response.body());
}
```
\=== "Kotlin"
```kotlin
// Parse the response body with kotlinx.serialization, Jackson, or similar — the snippet below only shows the status code branching
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
when (response.statusCode()) {
201 -> println("Ingest created: ${response.body()}")
// releaseId already ingested — if this is a retry after a timeout, treat the existing ingest in error.details as a success
409 -> println("Duplicate ingest: ${response.body()}")
// no ingest was created — fix the cause and resend
422 -> println("XML processing failed: ${response.body()}")
else -> System.err.println("Error ${response.statusCode()}: ${response.body()}")
}
```
\=== "C#"
```csharp
using System.Text.Json;
var response = await client.PostAsJsonAsync($"{baseUrl}/ddex/ingests", payload);
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadFromJsonAsync<JsonElement>();
var error = body.GetProperty("error");
if ((int)response.StatusCode == 409 && error.TryGetProperty("details", out var details))
{
// releaseId already ingested — if this is a retry after a timeout, treat the existing ingest as a success
Console.WriteLine($"Existing ingest: {details}");
}
else if ((int)response.StatusCode == 422)
{
// no ingest was created — fix the cause and resend
Console.WriteLine($"XML processing failed {error.GetProperty("code")}: {error.GetProperty("message")}");
}
else
{
Console.WriteLine($"Error {error.GetProperty("code")}: {error.GetProperty("message")} (requestId={error.GetProperty("requestId")})");
}
}
```
\=== "PHP"
```php
<?php
// $body and $status carry over from the basic example (curl_exec, CURLINFO_RESPONSE_CODE)
if ($status >= 400) {
$error = json_decode($body, true)["error"] ?? [];
if ($status === 409 && isset($error["details"])) {
// releaseId already ingested — if this is a retry after a timeout, treat the existing ingest as a success
echo "Existing ingest: " . json_encode($error["details"]);
} elseif ($status === 422) {
// no ingest was created — fix the cause and resend
echo "XML processing failed {$error['code']}: {$error['message']}";
} else {
echo "Error {$error['code']}: {$error['message']} (requestId={$error['requestId']})";
}
}
```
\=== "Ruby"
```ruby
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
error = JSON.parse(response.body)["error"] || {}
case response.code.to_i
when 409
# releaseId already ingested — if this is a retry after a timeout, treat the existing ingest as a success
puts "Existing ingest: #{error["details"]}"
when 422
# no ingest was created — fix the cause and resend
puts "XML processing failed #{error["code"]}: #{error["message"]}"
else
puts "Error #{error["code"]}: #{error["message"]} (requestId=#{error["requestId"]})"
end
end
```
\=== "Rust"
```rust
let response = client
.post("https://api.kitbetter.com/partner/v1/ddex/ingests")
.header("X-API-Key", env::var("KIT_API_KEY")?)
.json(&payload)
.send()?;
let status = response.status();
if !status.is_success() {
let body: serde_json::Value = response.json()?;
let error = &body["error"];
if status.as_u16() == 409 && !error["details"].is_null() {
// releaseId already ingested — if this is a retry after a timeout, treat the existing ingest as a success
println!("Existing ingest: {}", error["details"]);
} else if status.as_u16() == 422 {
// no ingest was created — fix the cause and resend
println!("XML processing failed {}: {}", error["code"], error["message"]);
} else {
println!("Error {}: {} (requestId={})", error["code"], error["message"], error["requestId"]);
}
}
```
\=== "C++"
```cpp
// Uses nlohmann/json for JSON parsing (vcpkg install nlohmann-json)
#include <nlohmann/json.hpp>
if (response.status_code >= 400) {
auto error = nlohmann::json::parse(response.text)["error"];
if (response.status_code == 409 && error.contains("details")) {
// releaseId already ingested — if this is a retry after a timeout, treat the existing ingest as a success
std::cout << "Existing ingest: " << error["details"] << std::endl;
} else if (response.status_code == 422) {
// no ingest was created — fix the cause and resend
std::cout << "XML processing failed " << error["code"] << ": " << error["message"] << std::endl;
} else {
std::cerr << "Error " << error["code"] << ": " << error["message"]
<< " (requestId=" << error["requestId"] << ")" << std::endl;
}
}
```
- Auth header rules: Using your key in requests
- Status codes and error body format: Error codes
- Handling
429(retries and backoff): Rate limits