Juniro API Mandate
Effective: January 2026 Status: Active Applies to: All Juniro services, teams, and integrations
The Mandate
Inspired by the famous "Bezos API Mandate" that transformed Amazon into a services company and led to AWS, Juniro adopts the following non-negotiable principles:
1. All Services Expose APIs
Every Juniro service, feature, and data source must expose its functionality through well-documented APIs. No exceptions.
2. Services Communicate Only Through APIs
Teams and services must communicate with each other exclusively through these APIs. No direct database access, no shared memory, no backdoors.
3. APIs Are Designed to Be Externalizable
Every API must be designed from the ground up as if it will be exposed to external partners. This means:
- Clear, consistent naming
- Comprehensive documentation
- Proper authentication and authorization
- Versioning strategy
- Rate limiting and quotas
4. Documentation Is Not Optional
An undocumented API does not exist. All APIs must have:
- OpenAPI 3.1 specification
- Interactive documentation (Scalar)
- Request/response examples
- Error code documentation
- Authentication instructions
5. Internal Quality = External Quality
There is no "internal-only" excuse for poor API design. Internal APIs must meet the same quality bar as partner-facing APIs.
Why This Matters
Without API Mandate: With API Mandate:
├── Spaghetti integrations ├── Clear service boundaries
├── Tribal knowledge ├── Self-documenting systems
├── "Ask John how X works" ├── Read the API docs
├── Fragile dependencies ├── Contract-based integration
├── Can't scale team ├── Teams work independently
└── Can't expose to partners └── Partner-ready from day one
Business Benefits
| Benefit | Description |
|---|---|
| Partner Ecosystem | Any API can become a partner API with minimal effort |
| Team Scalability | New teams integrate via APIs, not meetings |
| Quality Enforcement | Documentation requirement catches bad design early |
| Future-Proofing | Mobile apps, partner widgets, acquisitions - all use the same APIs |
API Tiers
Juniro APIs are organized into three tiers based on audience:
Tier 1: Internal APIs
Audience: Juniro services and frontends only
Auth: Supabase JWT (user context) or service tokens
Docs: /docs (requires internal auth)
Examples: User management, admin operations, internal analytics
Tier 2: Partner APIs
Audience: Approved partners and integrators
Auth: Partner API keys + optional user OAuth
Docs: /partner/docs (API key required)
Examples: Activity search, booking creation, webhook subscriptions
Restrictions: Rate limited, curated subset of Tier 1
Tier 3: Public APIs
Audience: Anyone (open data)
Auth: None or optional API key for higher limits
Docs: /public/docs (no auth required)
Examples: Public activity listings, provider profiles
Restrictions: Read-only, heavily cached, strict rate limits
Tier Matrix
| Capability | Internal | Partner | Public |
|---|---|---|---|
| Read activities | ✅ | ✅ | ✅ |
| Read providers | ✅ | ✅ | ✅ |
| Create bookings | ✅ | ✅ | ❌ |
| Read own bookings | ✅ | ✅ | ❌ |
| Read all bookings | ✅ | ❌ | ❌ |
| User management | ✅ | ❌ | ❌ |
| Admin operations | ✅ | ❌ | ❌ |
| Webhooks | ✅ | ✅ | ❌ |
| Rate limit | 1000/min | 100/min | 20/min |
API Standards
Naming Conventions
✅ DO:
/v1/activities
/v1/activities/{id}
/v1/activities/{id}/sessions
/v1/providers/{id}/activities
❌ DON'T:
/v1/getActivities
/v1/activity_list
/v1/Activities
/v1/activities/{id}/get-sessions
HTTP Methods
| Method | Usage | Idempotent |
|---|---|---|
| GET | Read resource(s) | Yes |
| POST | Create resource | No |
| PATCH | Partial update | Yes |
| PUT | Full replace | Yes |
| DELETE | Remove resource | Yes |
Response Format
All responses follow this structure:
// Success response
{
"success": true,
"data": { ... },
"meta": {
"page": 1,
"limit": 20,
"total": 100
}
}
// Error response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Human readable message",
"details": [
{ "field": "email", "message": "Invalid email format" }
]
}
}
HTTP Status Codes
| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PATCH, DELETE |
| 201 | Created | Successful POST |
| 400 | Bad Request | Validation error, malformed request |
| 401 | Unauthorized | Missing or invalid auth |
| 403 | Forbidden | Valid auth but insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate, state conflict |
| 422 | Unprocessable | Business logic error |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Unexpected error (log and alert) |
Versioning
URL versioning (required):
/v1/activities
/v2/activities
Header versioning (not used):
Accept: application/vnd.juniro.v1+json ❌
Version policy:
- v1 is the current stable version
- Breaking changes require new version (v2)
- Old versions supported for 12 months after deprecation
- Non-breaking changes added to current version
Pagination
// Request
GET /v1/activities?page=2&limit=20
// Response
{
"success": true,
"data": [...],
"meta": {
"page": 2,
"limit": 20,
"total": 156,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
}
}
Filtering & Sorting
// Filtering
GET /v1/activities?city=Austin&category=art&ageMin=5
// Sorting
GET /v1/activities?sort=price_asc
GET /v1/activities?sort=created_at_desc
// Combined
GET /v1/activities?city=Austin&sort=rating_desc&limit=10
Error Codes
All error codes are documented and consistent:
// Standard error codes
enum ErrorCode {
// Validation (400)
VALIDATION_ERROR = 'VALIDATION_ERROR',
INVALID_INPUT = 'INVALID_INPUT',
// Auth (401, 403)
UNAUTHORIZED = 'UNAUTHORIZED',
FORBIDDEN = 'FORBIDDEN',
TOKEN_EXPIRED = 'TOKEN_EXPIRED',
// Not Found (404)
NOT_FOUND = 'NOT_FOUND',
RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
// Conflict (409)
DUPLICATE = 'DUPLICATE',
ALREADY_EXISTS = 'ALREADY_EXISTS',
// Business Logic (422)
BOOKING_FULL = 'BOOKING_FULL',
REGION_MISMATCH = 'REGION_MISMATCH',
PAYMENT_REQUIRED = 'PAYMENT_REQUIRED',
// Rate Limit (429)
RATE_LIMITED = 'RATE_LIMITED',
// Server (500)
INTERNAL_ERROR = 'INTERNAL_ERROR',
}
Documentation Structure
Endpoint Documentation
Every API endpoint in Scalar/OpenAPI includes:
/v1/activities/{id}:
get:
summary: Get activity by ID
description: |
Retrieves detailed information about a specific activity,
including provider info, sessions, and reviews summary.
tags:
- Activities
- partner # Exposed to partners
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: Activity UUID
responses:
200:
description: Activity found
content:
application/json:
schema:
$ref: '#/components/schemas/ActivityResponse'
examples:
art-class:
summary: Art class example
value:
success: true
data:
id: "123e4567-e89b-12d3-a456-426614174000"
title: "Creative Art Explorers"
...
404:
description: Activity not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Documentation URLs
| Environment | Internal Docs | Partner Docs | OpenAPI Spec |
|---|---|---|---|
| Local | localhost:3000/docs | localhost:3000/partner/docs | localhost:3000/openapi.json |
| Staging | api.staging.juniro.com/docs | api.staging.juniro.com/partner/docs | api.staging.juniro.com/openapi.json |
| Production | api.juniro.com/docs | developers.juniro.com | api.juniro.com/openapi.json |
Scalar Integration
Why Scalar
| Feature | Swagger UI | Scalar |
|---|---|---|
| Modern UI | ❌ | ✅ |
| Dark mode | ❌ | ✅ |
| Try it now | ✅ | ✅ |
| Code samples | Basic | Multiple languages |
| Search | Basic | Full-text |
| Mobile friendly | ❌ | ✅ |
| Customizable | Limited | Fully themeable |
| Cost | Free | Free (self-hosted) |
Implementation
// src/docs/index.ts
import { apiReference } from '@scalar/hono-api-reference'
export const scalarConfig = {
theme: 'purple',
layout: 'modern',
darkMode: true,
hiddenClients: ['unirest'],
defaultHttpClient: {
targetKey: 'javascript',
clientKey: 'fetch',
},
metaData: {
title: 'Juniro API',
description: 'API documentation for Juniro marketplace',
ogImage: 'https://juniro.com/og-api-docs.png',
},
}
// Internal docs (all endpoints)
export const internalDocs = apiReference({
spec: { url: '/openapi.json' },
...scalarConfig,
metaData: {
...scalarConfig.metaData,
title: 'Juniro API (Internal)',
},
})
// Partner docs (filtered)
export const partnerDocs = apiReference({
spec: { url: '/partner/openapi.json' },
...scalarConfig,
metaData: {
...scalarConfig.metaData,
title: 'Juniro Partner API',
description: 'Integrate Juniro activities into your platform',
},
})
Route Tagging for Filtering
// Tag routes for different audiences
const activityRoutes = createRoute({
method: 'get',
path: '/v1/activities',
tags: ['Activities', 'partner', 'public'], // Exposed to all tiers
// ...
})
const adminRoutes = createRoute({
method: 'get',
path: '/v1/admin/users',
tags: ['Admin'], // Internal only (no 'partner' or 'public' tag)
// ...
})
Consuming APIs
Option 1: Scalar UI (Browser)
1. Navigate to /docs (internal) or /partner/docs (partner)
2. Browse endpoints in sidebar
3. Click "Try it" to test
4. Authentication: Click "Auth" → Enter JWT or API key
5. Send request and see response
Option 2: Export to Postman
# Download OpenAPI spec
curl https://api.juniro.com/openapi.json -o juniro-api.json
# Import into Postman
1. Open Postman
2. File → Import
3. Select juniro-api.json
4. Collection created with all endpoints
Option 3: Postman Dynamic Import
1. Open Postman
2. APIs → Create API → Import
3. Enter URL: https://api.juniro.com/openapi.json
4. Enable "Auto-sync" to keep updated
Option 4: Scalar Cloud (Partner Portal)
For public partner documentation:
1. Create account at scalar.com
2. Create new API reference
3. Connect to: https://api.juniro.com/partner/openapi.json
4. Customize branding
5. Publish to: https://developers.juniro.com
6. Features:
- Custom domain
- Analytics (who's reading what)
- API key management
- Changelog notifications
Option 5: Generate Client SDKs
# Generate TypeScript client
npx openapi-typescript-codegen \
--input https://api.juniro.com/openapi.json \
--output ./src/api-client \
--client fetch
# Generate Python client
openapi-generator generate \
-i https://api.juniro.com/openapi.json \
-g python \
-o ./juniro-client-python
Enforcement
Code Review Checklist
Every API PR must verify:
- OpenAPI spec updated
- All parameters documented
- Request/response examples included
- Error responses documented
- Appropriate tags assigned (internal/partner/public)
- Follows naming conventions
- Proper HTTP methods and status codes
- Rate limiting configured
Automated Checks
# CI/CD pipeline checks
- name: Validate OpenAPI Spec
run: npx @redocly/cli lint openapi.json
- name: Check Breaking Changes
run: npx oasdiff breaking base.json new.json
- name: Ensure All Routes Documented
run: npm run check:undocumented-routes
Quality Metrics
| Metric | Target | Measurement |
|---|---|---|
| Documentation coverage | 100% | All routes have OpenAPI spec |
| Example coverage | 100% | All endpoints have examples |
| Error documentation | 100% | All error codes documented |
| Breaking changes | 0 per release | Automated diff check |
Summary
The Juniro API Mandate in one sentence:
"Every feature is an API, every API is documented,
every document is a potential partner integration."
Build for external from day one. Your future self (and partners) will thank you.
Related Documents
- API Architecture - Technical implementation details
- AI Architecture - AI/ML API endpoints