🔌 Monster API
Monster has a REST API! Feed it domains programmatically and get certificate pins back in any format.
Endpoint
GET /api/pin/:domains
Parameters
| Parameter | Type | Description |
|---|---|---|
:domains | path | Comma-separated domains (max 10) |
format | query | Output format — comma-separated for multiple (e.g. android,ios) |
Accept | header | Content negotiation alternative to format |
Formats
| Format | Content-Type | Description |
|---|---|---|
plain | text/plain | Human-readable text (default) |
application/json | application/json | JSON array of pin objects |
csv | text/csv | CSV with host, subject, hash columns |
xml | application/xml | XML document |
yaml | text/yaml | YAML document |
android | application/xml | Android network-security-config.xml |
ios | application/xml | iOS TrustKit plist configuration |
Multiple Formats
Request several formats at once with comma-separated values. Monster returns a JSON object keyed by format name:
curl "https://pinning.monster/api/pin/google.com?format=android,ios"
# Returns: { "android": "<network-security-config>...", "ios": "<plist>..." }
Code Examples
# Plain text (default)curl https://pinning.monster/api/pin/google.com# JSON formatcurl https://pinning.monster/api/pin/google.com?format=application/json# Multiple domainscurl https://pinning.monster/api/pin/google.com,github.com# Android network security configcurl https://pinning.monster/api/pin/google.com?format=android# iOS TrustKit configcurl https://pinning.monster/api/pin/google.com?format=ios# Content negotiationcurl -H "Accept: application/json" https://pinning.monster/api/pin/google.com
import requests# Get pins as JSONresponse = requests.get( "https://pinning.monster/api/pin/google.com", params={"format": "application/json"})pins = response.json()# Multiple domainsresponse = requests.get("https://pinning.monster/api/pin/google.com,github.com")print(response.text)# Android configresponse = requests.get( "https://pinning.monster/api/pin/google.com", params={"format": "android"})with open("network_security_config.xml", "w") as f: f.write(response.text)
// Fetch pins as JSONconst res = await fetch( "https://pinning.monster/api/pin/google.com?format=application/json");const pins = await res.json();console.log(pins);// Multiple domainsconst multi = await fetch( "https://pinning.monster/api/pin/google.com,github.com");console.log(await multi.text());// Content negotiationconst json = await fetch("https://pinning.monster/api/pin/google.com", { headers: { Accept: "application/json" },});
package mainimport ( "fmt" "io" "net/http")func main() { resp, err := http.Get( "https://pinning.monster/api/pin/google.com?format=application/json", ) if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body))}
import Foundationlet url = URL( string: "https://pinning.monster/api/pin/google.com?format=ios")!let task = URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { return } let config = String(data: data, encoding: .utf8)! print(config)}task.resume()// Keep playground aliveRunLoop.main.run(until: Date(timeIntervalSinceNow: 10))
import java.net.URLfun main() { // Get Android network security config val url = URL( "https://pinning.monster/api/pin/google.com?format=android" ) val config = url.readText() println(config) // Get pins as JSON val json = URL( "https://pinning.monster/api/pin/google.com?format=application/json" ).readText() println(json)}
require "net/http"require "json"# Get pins as JSONuri = URI("https://pinning.monster/api/pin/google.com?format=application/json")response = Net::HTTP.get(uri)pins = JSON.parse(response)puts pins# Multiple domainsuri = URI("https://pinning.monster/api/pin/google.com,github.com")puts Net::HTTP.get(uri)# Save Android config to fileuri = URI("https://pinning.monster/api/pin/google.com?format=android")File.write("network_security_config.xml", Net::HTTP.get(uri))
import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;public class PinFetcher { public static void main(String[] args) throws Exception { var client = HttpClient.newHttpClient(); // Get pins as JSON var request = HttpRequest.newBuilder() .uri(URI.create( "https://pinning.monster/api/pin/google.com?format=application/json" )) .build(); var response = client.send( request, HttpResponse.BodyHandlers.ofString() ); System.out.println(response.body()); }}
Interactive Docs & OpenAPI
Want to try the API right in your browser? Open the Swagger UI playground — it lets you pick formats, enter domains, and fire real requests. You can also grab the machine-readable OpenAPI 3.0 spec to generate clients, import into Postman, or feed your own tooling.
CI/CD Integration
Keep your certificate pins fresh automatically! Drop one of these into your pipeline to update pins on every build.
GitHub Actions
# .github/workflows/update-pins.yml
name: Update certificate pins
on:
schedule:
- cron: "0 6 * * 1" # Every Monday at 06:00 UTC
workflow_dispatch:
jobs:
update-pins:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Android
- name: Update Android pins
run: |
curl -sf "https://pinning.monster/api/pin/${{ vars.PIN_DOMAINS }}?format=android" \
-o app/src/main/res/xml/network_security_config.xml
# iOS (TrustKit)
- name: Update iOS pins
run: |
curl -sf "https://pinning.monster/api/pin/${{ vars.PIN_DOMAINS }}?format=ios" \
-o ios/TrustKit.plist
- name: Commit if changed
run: |
git diff --quiet || \
(git add -A && git commit -m "chore: update certificate pins" && git push)
GitLab CI
# .gitlab-ci.yml
update-pins:
stage: build
script:
- curl -sf "https://pinning.monster/api/pin/$PIN_DOMAINS?format=android"
-o app/src/main/res/xml/network_security_config.xml
- curl -sf "https://pinning.monster/api/pin/$PIN_DOMAINS?format=ios"
-o ios/TrustKit.plist
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
Fastlane
# fastlane/Fastfile
lane :update_pins do
require "net/http"
domains = ENV["PIN_DOMAINS"] || "api.example.com"
# Android
android = Net::HTTP.get(
URI("https://pinning.monster/api/pin/#{domains}?format=android")
)
File.write("app/src/main/res/xml/network_security_config.xml", android)
# iOS
ios = Net::HTTP.get(
URI("https://pinning.monster/api/pin/#{domains}?format=ios")
)
File.write("ios/TrustKit.plist", ios)
git_add(path: ".")
git_commit(
path: ".",
message: "chore: update certificate pins"
)
end
Shell script (any CI)
#!/bin/sh
# update-pins.sh — works in any CI that has curl
set -e
DOMAINS="api.example.com,cdn.example.com"
curl -sf "https://pinning.monster/api/pin/$DOMAINS?format=android,ios" | \
jq -r '.android' > network_security_config.xml
curl -sf "https://pinning.monster/api/pin/$DOMAINS?format=android,ios" | \
jq -r '.ios' > TrustKit.plist
echo "Pins updated for: $DOMAINS"
Tip: Store your domains in a CI variable (PIN_DOMAINS) so you can update the list without changing pipeline config. Schedule weekly runs — certificates rotate, but not daily.
Limits
- Maximum 10 domains per request
- 42 second timeout per domain
- IP addresses and private domains are rejected