Prerequisites
Add the Spring AI and Spring Boot starters to your Maven or Gradle project:
xml — pom.xml (Maven)
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Add your PodClaw API key to application.properties:
podclaw.api-key=pc_your_api_key_here
podclaw.base-url=https://podclaw.io/api
Define PodClaw Functions
Spring AI function calling works by registering Function<Request, Response> beans. The LLM decides when to call them based on the function description. The beans below expose show creation and episode publishing to your AI agent:
java — PodClawFunctions.java
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import java.util.Map;
import java.util.function.Function;
@Configuration
public class PodClawFunctions {
@Value("${podclaw.api-key}")
private String apiKey;
@Value("${podclaw.base-url}")
private String baseUrl;
public record PublishEpisodeRequest(
String showId,
String title,
String audioUrl,
String description
) {}
public record PublishEpisodeResponse(
String episodeId,
String episodeUrl,
boolean success
) {}
public record CreateShowRequest(
String title,
String description,
String category
) {}
public record CreateShowResponse(
String showId,
String rssUrl
) {}
@Bean
public Function<PublishEpisodeRequest, PublishEpisodeResponse> publishPodcastEpisode() {
return request -> {
RestClient client = RestClient.create();
Map<String, Object> body = Map.of(
"title", request.title(),
"description", request.description() != null ? request.description() : "",
"audio_url", request.audioUrl(),
"status", "published"
);
var response = client.post()
.uri(baseUrl + "/shows/" + request.showId() + "/episodes")
.header("Authorization", "Bearer " + apiKey)
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.body(body)
.retrieve()
.toEntity(Map.class)
.getBody();
return new PublishEpisodeResponse(
(String) response.get("id"),
(String) response.get("url"),
true
);
};
}
@Bean
public Function<CreateShowRequest, CreateShowResponse> createPodcastShow() {
return request -> {
RestClient client = RestClient.create();
Map<String, Object> body = Map.of(
"title", request.title(),
"description", request.description(),
"category", request.category() != null ? request.category() : "Technology"
);
var response = client.post()
.uri(baseUrl + "/shows")
.header("Authorization", "Bearer " + apiKey)
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.body(body)
.retrieve()
.toEntity(Map.class)
.getBody();
return new CreateShowResponse(
(String) response.get("id"),
(String) response.get("rss_url")
);
};
}
}
Call from a Spring AI ChatClient
Register the function names in your ChatClient request options. The LLM automatically calls the appropriate function when your prompt requires it:
java — PodcastService.java
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class PodcastService {
private final ChatClient chatClient;
public PodcastService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String publishWeeklyEpisode(String showId, String audioUrl, String topic) {
return chatClient.prompt()
.user(String.format(
"Publish a podcast episode for show %s. " +
"The audio is at %s. " +
"Write a compelling title and description about: %s",
showId, audioUrl, topic
))
.functions("publishPodcastEpisode")
.call()
.content();
}
}
Tip: Pass both createPodcastShow and publishPodcastEpisode as functions in a single call. The LLM will call them in sequence if your prompt asks it to create a new show and immediately publish an episode.
What PodClaw handles automatically
After the episode is published via the API, PodClaw manages distribution:
- The RSS 2.0 feed is updated immediately with the new episode GUID
- Apple Podcasts and Spotify index the episode within hours
- A public episode page is created at the returned
episodeUrl
- Download counts and listener geography are tracked from first play