-
Notifications
You must be signed in to change notification settings - Fork 3
Vendor API Integration Guide
This guide explains how vendors authenticate with the SDKMAN! State API using JWT tokens, and how to integrate this into your release pipeline.
The State API uses JWT (JSON Web Token) authentication. The flow is:
- Exchange your vendor credentials for a short-lived JWT token
- Use the token as a Bearer token on all subsequent API calls
- Request a new token when it expires (tokens are valid for 10 minutes)
- A vendor account provisioned by the SDKMAN! team (email + password)
- The base URL of the State API (provided during onboarding)
curl -s -X POST https://state.sdkman.io/login \
-H "Content-Type: application/json" \
-d '{
"email": "vendor@example.com",
"password": "your-password"
}'Success (200):
{
"token": "eyJhbGciOiJIUzI1NiIs..."
}Possible errors:
| Status | Meaning |
|---|---|
401 |
Invalid credentials |
429 |
Rate limit exceeded (5 attempts/min) - wait and retry |
Include the token as a Bearer token in the Authorization header:
TOKEN="eyJhbGciOiJIUzI1NiIs..."
# Publish a version
curl -s -X POST https://state.sdkman.io/versions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"candidate": "gradle",
"version": "9.7.1",
"platform": "UNIVERSAL",
"url": "https://services.gradle.org/distributions/gradle-9.7.1-all.zip"
}'Most candidates ship a single, platform-independent distribution — use
"platform": "UNIVERSAL"and omitdistribution(that field is Java-specific, for vendors likeTEMURINorCORRETTO). See Universal Releases vs Multi-Platform Releases below for when each applies.
Success: 204 No Content
Tokens expire after 10 minutes. There are no refresh tokens - simply request a new one:
# Full example: login, capture token, publish
TOKEN=$(curl -sf -X POST https://state.sdkman.io/login \
-H "Content-Type: application/json" \
-d '{"email": "vendor@example.com", "password": "your-password"}' \
| jq -r '.token')
curl -sf -X POST https://state.sdkman.io/versions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"candidate": "gradle",
"version": "9.7.1",
"platform": "UNIVERSAL",
"url": "https://services.gradle.org/distributions/gradle-9.7.1-all.zip"
}'Store your credentials as repository secrets:
-
SDKMAN_VENDOR_EMAIL- your vendor email -
SDKMAN_VENDOR_PASSWORD- your vendor password
Most candidates (Gradle, Maven, Ant, SBT, and the like) ship a single, platform-independent archive. Publish once with platform: UNIVERSAL and no distribution:
name: Publish to SDKMAN!
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Authenticate with SDKMAN! State API
id: auth
run: |
TOKEN=$(curl -sf -X POST https://state.sdkman.io/login \
-H "Content-Type: application/json" \
-d "{\"email\": \"${{ secrets.SDKMAN_VENDOR_EMAIL }}\", \"password\": \"${{ secrets.SDKMAN_VENDOR_PASSWORD }}\"}" \
| jq -r '.token')
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"
- name: Publish version
run: |
curl -sf -X POST https://state.sdkman.io/versions \
-H "Authorization: Bearer ${{ steps.auth.outputs.token }}" \
-H "Content-Type: application/json" \
-d '{
"candidate": "gradle",
"version": "${{ github.event.release.tag_name }}",
"platform": "UNIVERSAL",
"url": "https://services.gradle.org/distributions/gradle-${{ github.event.release.tag_name }}-all.zip"
}'Note:
::add-mask::ensures the token is redacted from workflow logs.
For candidates distributed across multiple platforms (e.g. Java, where each vendor ships separate binaries per OS/architecture), authenticate once and publish each platform:
- name: Authenticate
id: auth
run: |
TOKEN=$(curl -sf -X POST https://state.sdkman.io/login \
-H "Content-Type: application/json" \
-d "{\"email\": \"${{ secrets.SDKMAN_VENDOR_EMAIL }}\", \"password\": \"${{ secrets.SDKMAN_VENDOR_PASSWORD }}\"}" \
| jq -r '.token')
echo "::add-mask::$TOKEN"
echo "token=$TOKEN" >> "$GITHUB_OUTPUT"
- name: Publish all platforms
env:
TOKEN: ${{ steps.auth.outputs.token }}
VERSION: ${{ github.event.release.tag_name }}
run: |
BASE_URL="https://state.sdkman.io"
for platform in LINUX_X64 LINUX_ARM64 MAC_X64 MAC_ARM64 WINDOWS_X64; do
curl -sf -X POST "$BASE_URL/versions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"candidate\": \"java\",
\"version\": \"$VERSION\",
\"platform\": \"$platform\",
\"distribution\": \"TEMURIN\",
\"url\": \"https://github.com/adoptium/temurin21-binaries/releases/download/$VERSION/OpenJDK21U-jdk_${platform}.tar.gz\"
}"
echo "Published $platform"
donecurl -sf -X DELETE https://state.sdkman.io/versions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"candidate": "gradle",
"version": "9.7.1",
"platform": "UNIVERSAL"
}'curl -sf -X DELETE https://state.sdkman.io/versions/tags \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"candidate": "gradle",
"tag": "latest",
"platform": "UNIVERSAL"
}'| Status | Meaning |
|---|---|
204 |
Success (no content) |
400 |
Validation error (check request body) |
401 |
Missing, invalid, or expired token |
403 |
Token valid but not authorised for this candidate |
404 |
Resource not found |
429 |
Rate limit exceeded on login (5 attempts/min per IP) |
The manual curl-based workflow described above is an interim integration path. We are retrofitting the sdkman-release-action to handle JWT authentication transparently under the hood.
Once available, your GitHub Actions workflow will simplify to something like:
- name: Publish to SDKMAN!
uses: sdkman/sdkman-release-action@main
with:
email: ${{ secrets.SDKMAN_VENDOR_EMAIL }}
password: ${{ secrets.SDKMAN_VENDOR_PASSWORD }}
candidate: gradle
version: ${{ github.event.release.tag_name }}
platform: UNIVERSAL
url: https://example.com/releases/gradle-9.7.1-all.zipNo manual token management, no curl. The action will handle login, token lifecycle, and error handling for you.
Watch the sdkman-release-action repo for updates.
Join the SDKMAN! community on Discord - head to the #vendors channel.