Skip to content

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;
    }
}
```