Partner APIDocumentation
Partner API v2

Juniob Partner API

Integrate Juniob developer reports directly into your platform. Sign a JWT on your server, make a REST call, get structured hiring data back.

Base URLhttps://api.juniob.io/partner

What Is the Partner API?

The Juniob Partner API gives trusted third-party platforms secure access to developer assessment reports. Whether you are building an ATS, a training platform, or a job board, you can show Juniob hiring insights inside your product with minimal integration effort.

Principles

  • Security by design. Every request is signed with a partner secret. Tokens are short-lived and generated server-side.
  • Developer consent. Data is only shared for developers who have connected their Juniob account to your platform.
  • Minimal surface area. Use the sections query param to fetch only what you need.
  • No session overhead. No OAuth, no refresh tokens. Sign a JWT and call the API.

What You Can Do

  • Fetch assessment reports with selective sections
  • Generate signed view URLs so recruiters can open the full report in a tab

JWT Bearer Auth

The endpoints /developer and /generate-redirection-url require a Bearer JWT signed with your partner secret, provided by Juniob. Always sign tokens on your backend - never expose your secret in client code.

⚠️Sign tokens server-side only. Use short expiry times (5 to 15 minutes) for API call tokens.
HTTP
Authorization: Bearer <your-signed-jwt>

JWT Structure

Your token payload must include these three fields:

FieldTypeRequiredDescription
orgstringYesYour organization name as registered with Juniob.
developerEmailstringYesEmail of the developer. Must match their Juniob account email (case-insensitive).
externalUserIdstring | numberYesID of the requesting user on your platform (e.g. recruiter ID). Used for billing deduplication.
sign-token.jsJavaScript
const jwt = require("jsonwebtoken");

const token = jwt.sign(
  {
    org: "acme-corp",
    developerEmail: "[email protected]",
    externalUserId: "recruiter-42",
  },
  process.env.JUNIOB_PARTNER_SECRET,
  { expiresIn: "10m" }
);

GET /partner/developer

GET/partner/developerRequires Auth

Retrieve a structured assessment report for the developer identified in the JWT. Use the sections query parameter to fetch only what you need.

Query Parameters

ParameterTypeRequiredDescription
sectionsstringNoComma-separated section names: profile, summary, projects, technical, risk, interview, growth. Omit to return all seven sections.

Example Request

JavaScript
const res = await fetch(
  "https://api.juniob.io/partner/developer?sections=profile,summary",
  { headers: { Authorization: `Bearer ${token}` } }
);
const data = await res.json();

Response - ASSESSED

JSON
{
  "status": "ASSESSED",

  "profile": {
    "email": "[email protected]",
    "firstName": "Sarah",
    "lastName": "Anderson",
    "location": "Paris, France",
    "developerType": "FULLSTACK",       // FRONTEND | BACKEND | FULLSTACK | MOBILE
    "learningBackground": "BOOTCAMP",   // CS_DEGREE | RELATED_DEGREE | BOOTCAMP | SELF_TAUGHT | CAREER_CHANGE
    "totalCodingMonths": 18,
    "techExperiences": [
      { "stackName": "React", "months": 14 },
      { "stackName": "TypeScript", "months": 10 }
    ]
  },

  "summary": {
    "recommendation": "SAFE_TO_INTERVIEW",
    "recommendationReasons": ["Clean architecture", "TypeScript discipline"],
    "overallScore": 74,
    "juniorLevel": "ABOVE_EXPECTED",
    "juniorLevelContext": "Performs well above average junior level",
    "conclusion": "Strong candidate for a mid-level track...",
    "timeToProductivity": "2-4 weeks",
    "keyInsights": [...]
  },
}

Response - NOT_ASSESSED

status tells you the report is not ready. assessmentStatus tells you where the developer currently is in the Juniob pipeline, so you can decide whether to retry later or consider them unready.

JSON
{
  "status": "NOT_ASSESSED",
  "assessmentStatus": "ANALYZING"
  // assessmentStatus possible values:
  // REGISTERING         — profile incomplete, not yet submitted
  // PROJECTS_SUBMITTED  — submitted, analysis not started yet
  // ANALYZING           — analysis in progress
  // PENDING_ANALYSIS    — analysis needs to be re-run (project was modified)
}
ℹ️If the developer email is not found in Juniob, the server returns HTTP 204 No Content. No billing is applied for 204 or NOT_ASSESSED responses.

GET /partner/generate-redirection-url

GET/partner/generate-redirection-urlRequires Auth

Generate a signed URL that opens the full Juniob developer report in a browser tab. The token is valid for 7 days and is scoped to your partner app's secret - it can be safely sent in emails or stored in your system.

Example Request

JavaScript
const res = await fetch(
  "https://api.juniob.io/partner/generate-redirection-url",
  { headers: { Authorization: `Bearer ${token}` } }
);
const url = await res.json();
// "https://juniob.io/partner/view?accessViewToken=eyJ..."

window.open(url, "_blank");

Response

JSON
"https://juniob.io/partner/view?accessViewToken=eyJhbGciOiJIUzI1NiJ9..."
ℹ️The view token payload includes org, developerId, and externalUserId (carried from your original JWT). This means billing deduplication works correctly even if the recruiter uses both the API pull and the redirect link.

Available Sections

Pass any combination of these section names in the sections query parameter on /partner/developer. Omit the parameter entirely to receive all seven sections. Sections not requested are returned as null.

profile
Email, name, location, developer type, learning background, coding experience, tech stack list.
summary
Hiring recommendation, overall score, junior level, key insights, conclusion, time-to-productivity.
projects
Per-project breakdown: score, tech stack, strengths, areas for improvement, best practices, commit analysis.
technical
Technical skill breakdown and tech proficiency scores per stack.
risk
Authenticity analysis, risk flags, red flags, and aggregated commit history statistics.
interview
Tailored interview questions generated from the developer's actual projects.
growth
Onboarding timeline, training priorities, mentoring needs, and growth potential assessment.

Response Values

profile - PartnerProfileDto

FieldTypeDescription
emailstringDeveloper email address
firstNamestring?First name
lastNamestring?Last name
locationstring?Self-reported location
developerTypestring?FRONTEND | BACKEND | FULLSTACK | MOBILE
learningBackgroundstring?CS_DEGREE | RELATED_DEGREE | BOOTCAMP | SELF_TAUGHT | CAREER_CHANGE
totalCodingMonthsnumber?Total months of coding experience
techExperiences{ stackName, months }[]Technologies with experience duration

summary - PartnerSummaryDto

FieldTypeDescription
recommendationstringHiring recommendation text
recommendationReasonsstring[]Bullet-point reasons
overallScorenumber0–100 assessment score
juniorLevelstringABOVE_EXPECTED | WITHIN_EXPECTED | BELOW_EXPECTED
juniorLevelContextstring?Narrative explanation of the level
keyInsightsunknownTop insights about the candidate
conclusionstringFree-text hiring conclusion
timeToProductivitystring?Estimated ramp-up time, e.g. "2-4 weeks"

projects - PartnerProjectDto[]

FieldTypeDescription
namestringProject name
projectTypestringType of project
techStackstring[]Technologies used
scorenumberProject quality score
strengthsstring[]Identified strengths
areasForImprovementstring[]Areas needing improvement
bestPracticesstring[]Best practices observed
commitAnalysisPartnerCommitAnalysisDto?Commit discipline, authenticity impact, observations

technical - PartnerTechnicalDto

FieldTypeDescription
technicalBreakdownunknownDetailed breakdown by technical category
techProficiencyRecord<string, number>?Stack → proficiency score map, e.g. { React: 8, TypeScript: 7 }

risk - PartnerRiskDto

FieldTypeDescription
authenticityAnalysisunknownWhether the work appears genuine
riskFlagsstring[]Minor risk indicators
redFlagsstring[]Significant concerns requiring follow-up
commitHistoryAggregateunknownAggregated commit stats across all analyzed projects

interview - PartnerInterviewDto

FieldTypeDescription
interviewQuestionsstring[]Tailored questions based on the developer's projects

growth - PartnerGrowthDto

FieldTypeDescription
onboardingTimelineunknownStructured onboarding plan
trainingPrioritiesstring[]Key skills to develop first
mentoringNeedsstring[]Areas where mentoring is recommended
growthPotentialstring?Assessment of long-term growth potential

Billing

Juniob bills per unique recruiter × developer pair, identified by externalUserId × developerId. Access is logged on the first successful retrieval of an ASSESSED report. Subsequent calls for the same pair - whether via API pull or redirect link - are free.

TriggerResultBilled?
/developer → NOT_ASSESSEDNo report availableNo
/developer → ASSESSEDReport deliveredYes - once
Same recruiter: API pull + redirect linkDeduplication via upsertStill once
ℹ️Use stable, consistent externalUserId values for your recruiters to ensure correct deduplication across both integration patterns.

Error Handling

StatusScenarioBody
401Missing Authorization headerMissing token
401JWT cannot be decodedInvalid token format
401Signature invalid or token expiredInvalid or expired token
403org not found or partner not approvedUnauthorized partner
403Developer profile set to privateDeveloper profile not visible
204Developer email not found in Juniobempty body
200Developer not yet assessed{ status: 'NOT_ASSESSED', assessmentStatus: 'ANALYZING' | ... }
200Success{ status: 'ASSESSED', ...sections }

Node.js

Full integration example using jsonwebtoken and the native fetch API (Node 18+).

juniob-client.jsJavaScript
import jwt from "jsonwebtoken";

const BASE   = "https://api.juniob.io/partner";
const ORG    = process.env.JUNIOB_ORG;
const SECRET = process.env.JUNIOB_PARTNER_SECRET;

function signToken(developerEmail, externalUserId) {
  return jwt.sign(
    { org: ORG, developerEmail, externalUserId },
    SECRET,
    { expiresIn: "10m" }
  );
}

// ── Pull a selective report ──────────────────────────────────────────────────
async function getDeveloperReport(developerEmail, recruiterId, sections = []) {
  const token  = signToken(developerEmail, recruiterId);
  const params = sections.length ? `?sections=${sections.join(",")}` : "";

  const res = await fetch(`${BASE}/developer${params}`, {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (res.status === 204) return null; // developer not in Juniob

  return res.json(); // { status, profile?, summary?, ... }
}

// ── Generate a recruiter view URL ────────────────────────────────────────────
async function getViewUrl(developerEmail, recruiterId) {
  const token = signToken(developerEmail, recruiterId);

  const res = await fetch(`${BASE}/generate-redirection-url`, {
    headers: { Authorization: `Bearer ${token}` },
  });

  return res.json(); // "https://juniob.io/partner/view?accessViewToken=..."
}

// Usage
const report = await getDeveloperReport(
  "[email protected]",
  "recruiter-42",
  ["profile", "summary", "risk"]
);

if (report?.status === "ASSESSED") {
  console.log(report.summary.recommendation);    // "SAFE_TO_INTERVIEW"
  console.log(report.summary.overallScore);      // 74
  console.log(report.risk.redFlags);             // []
}

Python

Full integration example using PyJWT and requests.

juniob_client.pyPython
import os
import jwt
import requests
from datetime import datetime, timedelta, timezone

BASE   = "https://api.juniob.io/partner"
ORG    = os.environ["JUNIOB_ORG"]
SECRET = os.environ["JUNIOB_PARTNER_SECRET"]


def sign_token(developer_email: str, external_user_id: str) -> str:
    payload = {
        "org":            ORG,
        "developerEmail": developer_email,
        "externalUserId": external_user_id,
        "exp":            datetime.now(timezone.utc) + timedelta(minutes=10),
    }
    return jwt.encode(payload, SECRET, algorithm="HS256")


def get_developer_report(
    developer_email: str,
    recruiter_id: str,
    sections: list[str] = [],
) -> dict | None:
    token  = sign_token(developer_email, recruiter_id)
    params = {"sections": ",".join(sections)} if sections else {}

    resp = requests.get(
        f"{BASE}/developer",
        headers={"Authorization": f"Bearer {token}"},
        params=params,
    )

    if resp.status_code == 204:
        return None  # developer not in Juniob

    resp.raise_for_status()
    return resp.json()


def get_view_url(developer_email: str, recruiter_id: str) -> str:
    token = sign_token(developer_email, recruiter_id)

    resp = requests.get(
        f"{BASE}/generate-redirection-url",
        headers={"Authorization": f"Bearer {token}"},
    )
    resp.raise_for_status()
    return resp.json()


# Usage
report = get_developer_report(
    "[email protected]",
    "recruiter-42",
    ["profile", "summary", "risk"],
)

if report and report["status"] == "ASSESSED":
    print(report["summary"]["recommendation"])  # "SAFE_TO_INTERVIEW"
    print(report["summary"]["overallScore"])    # 74
    print(report["risk"]["redFlags"])           # []

Ready to integrate?

Request your partner signing secret and go live in minutes.