Score a company. Cached returns inline, cold returns a job.
curl --request POST \
--url https://api.keplerinsights.us/v1/score \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"domain": "stripe.com",
"force_fresh": false,
"wait": true
}
'import requests
url = "https://api.keplerinsights.us/v1/score"
payload = {
"domain": "stripe.com",
"force_fresh": False,
"wait": True
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({domain: 'stripe.com', force_fresh: false, wait: true})
};
fetch('https://api.keplerinsights.us/v1/score', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.keplerinsights.us/v1/score",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'domain' => 'stripe.com',
'force_fresh' => false,
'wait' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.keplerinsights.us/v1/score"
payload := strings.NewReader("{\n \"domain\": \"stripe.com\",\n \"force_fresh\": false,\n \"wait\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.keplerinsights.us/v1/score")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"domain\": \"stripe.com\",\n \"force_fresh\": false,\n \"wait\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.keplerinsights.us/v1/score")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"domain\": \"stripe.com\",\n \"force_fresh\": false,\n \"wait\": true\n}"
response = http.request(request)
puts response.read_body{
"domain": "<string>",
"scored_at": "2023-11-07T05:31:56Z",
"ki_rating": "KI-1+",
"composite_score": 50,
"buckets": {
"team_structure": 50,
"market_position": 50,
"momentum_tailwinds": 50,
"financial_health": 50
},
"mode": "<string>",
"scale_premium": 6,
"rank": {
"percentile": 50,
"cohort_size": 1
},
"x_kepler": {
"tier": "free",
"cache_status": "cached",
"freshness_window_hours": 123
}
}{
"job_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "pending",
"domain": "<string>",
"poll_url": "/v1/jobs/9f0c…"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}Endpoints
POST /v1/score
Score a company. Cached returns inline; cold returns a job to poll.
POST
/
v1
/
score
Score a company. Cached returns inline, cold returns a job.
curl --request POST \
--url https://api.keplerinsights.us/v1/score \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"domain": "stripe.com",
"force_fresh": false,
"wait": true
}
'import requests
url = "https://api.keplerinsights.us/v1/score"
payload = {
"domain": "stripe.com",
"force_fresh": False,
"wait": True
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({domain: 'stripe.com', force_fresh: false, wait: true})
};
fetch('https://api.keplerinsights.us/v1/score', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.keplerinsights.us/v1/score",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'domain' => 'stripe.com',
'force_fresh' => false,
'wait' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.keplerinsights.us/v1/score"
payload := strings.NewReader("{\n \"domain\": \"stripe.com\",\n \"force_fresh\": false,\n \"wait\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.keplerinsights.us/v1/score")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"domain\": \"stripe.com\",\n \"force_fresh\": false,\n \"wait\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.keplerinsights.us/v1/score")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"domain\": \"stripe.com\",\n \"force_fresh\": false,\n \"wait\": true\n}"
response = http.request(request)
puts response.read_body{
"domain": "<string>",
"scored_at": "2023-11-07T05:31:56Z",
"ki_rating": "KI-1+",
"composite_score": 50,
"buckets": {
"team_structure": 50,
"market_position": 50,
"momentum_tailwinds": 50,
"financial_health": 50
},
"mode": "<string>",
"scale_premium": 6,
"rank": {
"percentile": 50,
"cohort_size": 1
},
"x_kepler": {
"tier": "free",
"cache_status": "cached",
"freshness_window_hours": 123
}
}{
"job_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "pending",
"domain": "<string>",
"poll_url": "/v1/jobs/9f0c…"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}{
"error": "unauthorized",
"message": "<string>",
"reason": "<string>"
}The primary scoring entry point. Returns the latest score for
Branch on the response: if the body has
domain from cache when a stored record is within your tier’s freshness window, or starts a cold scoring job when no fresh record exists.
Two response shapes
- HTTP 200 — a cached score is available. Returned inline; ready to use.
- HTTP 202 — no fresh record. The server has started a cold scoring run and returned a
job_id. PollGET /v1/jobs/{job_id}untilstatus: "complete".
When you’ll see each path
- Cached (200) — the dominant path. Most calls hit it; tier-determined freshness windows are designed so that customer traffic mostly reuses recent work.
- Cold (202) — first time you’ve ever asked for
domain, or the most recent record is older than your tier’s window.
| Tier | Window |
|---|---|
| Free | 7 days (sandbox only — Free can’t run live cold) |
| Starter | 24 hours |
| Growth | 6 hours |
| Scale | 1 hour |
| Enterprise | 15 minutes |
Example (Python SDK)
The official SDK handles both shapes transparently.score() blocks until the score is ready, polling internally on the cold path.
from kepler_insights import Kepler
k = Kepler(api_key="ki_live_...")
score = k.score("stripe.com") # blocks up to 180s on cold
print(f"{score.domain}: {score.ki_rating} ({score.composite_score:.1f})")
# Non-blocking — get the Job and poll yourself:
job = k.start_score("acme-co.com")
print(job.id)
score = job.wait(timeout=240)
Example (curl, manual polling)
# 1. Submit
curl -X POST https://api.keplerinsights.us/v1/score \
-H "X-API-Key: ki_live_..." \
-H "Content-Type: application/json" \
-d '{"domain":"stripe.com"}'
# Cached response (200):
# { "domain": "stripe.com", "ki_rating": "KI-1", "composite_score": 73.65, ... }
# Cold response (202):
# { "job_id": "679a9e32-...", "status": "pending",
# "domain": "stripe.com", "poll_url": "/v1/jobs/679a9e32-..." }
# 2. Poll until terminal (cold only)
curl https://api.keplerinsights.us/v1/jobs/679a9e32-... \
-H "X-API-Key: ki_live_..."
composite_score, you have the score inline. If it has job_id, queue a poll loop on the poll_url.
Field notes
composite_scoreis 0–100, not 0–10. Easy mistake when reading.scale_premiumis a separate additive bonus (up to ~12 pts). It’s already applied tocomposite_score; the field is broken out so you can show “47.0 composite + 4.1 scale premium” if you want.rankis computed against the full universe Kepler has ever scored, not just the active cohort. Use/v1/company/{domain}/cohortfor sector-matched comparison.x_kepler.cache_statusis"cached"on every score body, including the one you fetch after a job completes — the fresh cold-pipeline result is written to cache before the response shape is constructed. The enum is reserved for a future sync-cold response shape; today only"cached"is emitted.waitparameter is accepted but ignored. Cold = async, always. The parameter is preserved so older client code doesn’t break.
Sandbox behavior
ki_test_ keys against the 4 canned domains (acme.test, unicorn.test, struggling.test, cohort.test) always return inline 200. Sandbox has no cold path — there’s nothing to queue. This means production code paths that branch on 202 won’t be exercised in the sandbox. Test the polling path against a fresh live domain in staging before relying on it.Authorizations
Live keys are prefixed ki_live_, test keys ki_test_. Issue + revoke
keys at https://console.keplerinsights.us. Never embed a key in client-side
code — every endpoint is backend-to-API only.
Body
application/json
Response
Cached score (fresh under your tier's window).
Available options:
KI-1+, KI-1, KI-2+, KI-2, KI-3, KI-4, KI-5 Required range:
0 <= x <= 100The 4 KI buckets. Each is 0–100.
Show child attributes
Show child attributes
Present and = sandbox only with a ki_test_ key.
Up to ~12 pts for established mega-caps; up to ~6 pts for growth profile.
Required range:
0 <= x <= 12Show child attributes
Show child attributes
Caller-facing metadata. Stable across versions.
Show child attributes
Show child attributes

