Developer Documentation

Build on Inlet

Everything you need to integrate with Inlet — REST API, MCP tools, webhooks, and SDKs. Automate regulatory compliance checks, manage products, and build custom workflows.

95API Endpoints
24+MCP Tools
ScopedAPI Keys

Getting Started

Get up and running with the Inlet API in three steps. All requests are scoped to your company via multi-tenant isolation.

Quick Start

1Get your API key

Sign in at inlet.run and go to Settings → API Keys to create a key. Keys are prefixed withink_live_ and never expire unless you set a custom expiry. API access requires the Growth or Enterprise plan.

2Make your first request

List your SKUs with a single call.

curl https://api.inlet.run/api/v1/skus/ \
  -H "Authorization: Bearer ink_live_your_key_here"

3Run a compliance check

Check any SKU against one or more markets. The engine runs all three compliance layers automatically.

curl -X POST https://api.inlet.run/api/v1/compliance/check \
  -H "Authorization: Bearer ink_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "sku_id": "550e8400-e29b-41d4-a716-446655440000",
    "markets": ["US", "EU"]
  }'

Base URL

text
https://api.inlet.run/api/v1

All endpoints are prefixed with /api/v1. All requests and responses use JSON unless otherwise noted.

The interactive OpenAPI explorer is available at api.inlet.run/docs.

Rate Limits

Starter

10 req/min

100 checks/mo

Growth

60 req/min

1,000 checks/mo

Enterprise

300 req/min

Unlimited

Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

Authentication

The Inlet API supports two authentication methods: JWT bearer tokens and long-lived API keys. Both are passed as Authorization: Bearer <token> headers. All endpoints accept either method interchangeably.

API Keys

Recommended

For server-to-server integrations, use long-lived API keys. Create them in Settings > API Keys or via the API. Keys are prefixed with ink_live_ and SHA-256 hashed at rest. The full key is only shown once at creation time.

Creating an API key

# Create an API key (requires admin or owner role)
curl -X POST https://api.inlet.run/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "CI Pipeline" }'

# Response (key is shown ONCE):
# {
#   "id": "key_uuid",
#   "key": "ink_live_a1b2c3d4e5f6g7h8i9j0...",
#   "name": "CI Pipeline",
#   "prefix": "ink_live_a1b2",
#   "created_at": "2026-04-12T10:00:00Z"
# }

Using an API key

# Use the key as a Bearer token on any endpoint
curl https://api.inlet.run/api/v1/skus/ \
  -H "Authorization: Bearer ink_live_a1b2c3d4e5f6..."

# API keys never expire (unless you set a custom expiry).
# Revoke instantly from Settings if compromised.

JWT Token Flow

For browser-based and short-lived sessions

Login with email and password to receive a JWT bearer token. Tokens are HS256-signed and expire after 60 minutes. Use the token in the Authorization header for subsequent requests.

# Step 1: Login to get a JWT token
curl -X POST https://api.inlet.run/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "dev@yourcompany.com",
    "password": "your-secure-password"
  }'

# Response:
# {
#   "access_token": "eyJhbGciOiJIUzI1NiIs...",
#   "token_type": "bearer"
# }

# Step 2: Use the token
curl https://api.inlet.run/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Google OAuth

For production sign-in

In production, all sign-ups go through Google OAuth. The flow is a standard OAuth 2.0 authorization code grant.

1

Get the OAuth URL

POST /auth/google/url returns a url to redirect the user to Google's consent screen.

2

Exchange the code

After the user approves, Google redirects back with a code. Send it to POST /auth/google/callback.

3

Receive the JWT

The callback returns an access_token just like the password login flow.

Auth Error Responses

401 Unauthorized

No valid credentials provided, or the token has expired.

json
{
  "detail": "Could not validate credentials"
}
403 Forbidden

Valid credentials, but your role lacks permission. For example, only admin/owner can create API keys.

json
{
  "detail": "Insufficient permissions. Required role: admin"
}

Security Notes

  • All data is scoped to your company via multi-tenant isolation
  • Both JWT tokens and API keys are accepted on all authenticated endpoints
  • API keys are SHA-256 hashed at rest -- the full key is only shown once at creation
  • JWT tokens expire after 60 minutes (HS256)
  • API access requires the Growth or Enterprise plan
  • Auth endpoints are rate limited at 10 requests per 5 minutes per IP

Pagination

List endpoints use cursor-based pagination for consistent, performant results even as data changes between pages.

Cursor-Based Pagination

Customer-facing list endpoints (SKUs, compliance history, ingredients, alerts, etc.) return cursor-paginated results. Cursors are opaque, Base64-encoded strings that encode a(created_at, id) tuple.

Request Parameters

cursorstring

Opaque cursor from the previous response's next_cursor. Omit for the first page.

page_sizeinteger

Number of items per page. Default: 50, maximum: 100.

Response Shape

json
{
  "items": [
    { "id": "uuid-1", "name": "Hydrating Face Cream", ... },
    { "id": "uuid-2", "name": "Vitamin C Serum", ... }
  ],
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0xMCIsImlkIjoiYWJjMTIzIn0=",
  "has_more": true,
  "total": 247
}

Paginating Through Results

# First page
curl "https://api.inlet.run/api/v1/skus/?page_size=20" \
  -H "Authorization: Bearer ink_live_..."

# Next page -- use next_cursor from previous response
curl "https://api.inlet.run/api/v1/skus/?page_size=20&cursor=eyJjcmVhdGVkX2F0..." \
  -H "Authorization: Bearer ink_live_..."

# Continue until has_more is false

Offset Pagination (Admin)

Admin endpoints (/ingredients/admin/all,/regulations/admin/all) use traditional offset-based pagination.

pageinteger

Page number (1-indexed). Default: 1.

page_sizeinteger

Items per page. Default: 50, maximum: 200.

json
{
  "items": [...],
  "meta": {
    "total": 1247,
    "page": 3,
    "page_size": 50,
    "total_pages": 25
  }
}

Errors

The Inlet API uses standard HTTP status codes. All errors return a JSON body with a detail field.

HTTP Status Codes

200OK

Request succeeded. Response body contains the result.

201Created

Resource created successfully. Response body contains the new resource.

400Bad Request

The request was malformed or missing required fields.

401Unauthorized

No valid authentication credentials provided.

403Forbidden

Valid credentials, but insufficient permissions for this action.

404Not Found

The requested resource does not exist or is not accessible.

409Conflict

The request conflicts with existing state (e.g. duplicate name).

422Validation Error

Request body failed Pydantic validation. See detail array for field-level errors.

429Too Many Requests

Rate limit exceeded. Check Retry-After header.

500Internal Server Error

Something went wrong on our end. Retry with exponential backoff.

Error Response Shapes

Standard error

Most errors return a simple string detail.

json
{
  "detail": "SKU not found"
}

Validation error (422)

When the request body fails Pydantic validation, the detail field is an array of field-level errors with location, message, and type.

json
{
  "detail": [
    {
      "loc": ["body", "category"],
      "msg": "Input should be 'skin_care', 'hair_care', 'makeup', 'fragrance', 'oral_care', 'food', 'beverage' or 'food_additive'",
      "type": "enum"
    },
    {
      "loc": ["body", "name"],
      "msg": "Field required",
      "type": "missing"
    }
  ]
}

Rate limit error (429)

When rate limited, check the Retry-After header (in seconds) before retrying.

json
// HTTP 429 Too Many Requests
// Retry-After: 30
{
  "detail": "Rate limit exceeded. Try again in 30 seconds."
}

Handling Errors in Code

import requests

resp = requests.post(
    "https://api.inlet.run/api/v1/compliance/check",
    headers={"Authorization": "Bearer ink_live_..."},
    json={"sku_id": "invalid-uuid", "markets": ["US"]},
)

if resp.status_code == 422:
    # Validation error -- field-level details
    for error in resp.json()["detail"]:
        field = " -> ".join(str(x) for x in error["loc"])
        print(f"{field}: {error['msg']}")
elif resp.status_code == 429:
    retry_after = int(resp.headers.get("Retry-After", 60))
    print(f"Rate limited. Retry in {retry_after}s")
elif resp.status_code >= 400:
    print(f"Error {resp.status_code}: {resp.json()['detail']}")
else:
    check = resp.json()

REST API Reference

95 endpoints across 15 resource groups. Base URL: https://api.inlet.run/api/v1

Code Examples

List SKUs with Filters

Fetch active SKUs in the skin_care category with pagination.

curl "https://api.inlet.run/api/v1/skus/?status=active&category=skin_care&page_size=20" \
  -H "Authorization: Bearer ink_live_your_key_here"

# Response:
# {
#   "items": [
#     { "id": "uuid", "name": "Hydrating Face Cream", "category": "skin_care", ... },
#     ...
#   ],
#   "next_cursor": "eyJjcmVhdGVkX2F0...",
#   "has_more": true,
#   "total": 47
# }

Create a SKU

Create a new product SKU with category and description.

curl -X POST https://api.inlet.run/api/v1/skus/ \
  -H "Authorization: Bearer ink_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Hydrating Face Cream SPF 30",
    "brand": "GlowLab",
    "category": "skin_care",
    "description": "Daily moisturizer with SPF protection",
    "status": "draft"
  }'

Bulk Compliance Check

Check multiple SKUs against multiple markets in a single request. The engine runs all three compliance layers automatically.

curl -X POST https://api.inlet.run/api/v1/compliance/check/bulk \
  -H "Authorization: Bearer ink_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "sku_ids": [
      "550e8400-e29b-41d4-a716-446655440000",
      "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
    ],
    "markets": ["US", "EU", "CA"]
  }'

Search Ingredients with Fuzzy Matching

Search the master ingredient database by INCI name, CAS number, or common name. Use the fuzzy endpoint for approximate matching.

# Exact search by INCI/CAS
curl "https://api.inlet.run/api/v1/ingredients/search?q=retinol&limit=5" \
  -H "Authorization: Bearer ink_live_your_key_here"

# Fuzzy matching (handles typos/variations)
curl "https://api.inlet.run/api/v1/ingredients/fuzzy?name=retinoll" \
  -H "Authorization: Bearer ink_live_your_key_here"

# Response:
# {
#   "matches": [
#     { "ingredient": { "inci_name": "Retinol", ... }, "score": 0.95 },
#     { "ingredient": { "inci_name": "Retinol Palmitate", ... }, "score": 0.78 }
#   ]
# }

Upload and Parse a Product File

Upload a PDF, image, CSV, or XLSX file and extract ingredient information automatically. The AI parser matches extracted names against the master ingredient database using fuzzy matching.

curl -X POST https://api.inlet.run/api/v1/skus/parse-file \
  -H "Authorization: Bearer ink_live_your_key_here" \
  -F "file=@product-spec.pdf"

# Response includes matched and candidate ingredients:
# {
#   "items": [
#     {
#       "raw_name": "Aqua",
#       "matched_ingredient": { "id": "uuid", "inci_name": "Aqua" },
#       "confidence": 1.0,
#       "candidates": []
#     },
#     {
#       "raw_name": "Retinoll",
#       "matched_ingredient": { "id": "uuid", "inci_name": "Retinol" },
#       "confidence": 0.92,
#       "candidates": [
#         { "id": "uuid2", "inci_name": "Retinol Palmitate", "score": 0.78 }
#       ]
#     }
#   ]
# }

Create a Task

Create a compliance review task linked to a specific SKU and market.

curl -X POST https://api.inlet.run/api/v1/tasks/ \
  -H "Authorization: Bearer ink_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Review EU compliance for SPF 30 Cream",
    "task_type": "compliance_review",
    "priority": "high",
    "sku_id": "550e8400-e29b-41d4-a716-446655440000",
    "market_code": "EU"
  }'

List Alerts by Severity

Fetch critical and high-severity alerts for a specific market.

curl "https://api.inlet.run/api/v1/alerts/?severity=critical,high&market_code=EU" \
  -H "Authorization: Bearer ink_live_your_key_here"

Admin & Staff Endpoints

Additional admin endpoints for regulation management, ingredient review, cross-tenant queries, training data, and scraper control are available via the interactive OpenAPI explorer. These are restricted to staff accounts.

Objects

Reference documentation for the key API objects. Expand any object to see its full field schema and an example JSON representation.

The SKU object

Represents a product (Stock Keeping Unit) with its formulation, claims, and lifecycle state.

id
string (uuid)

Unique identifier for the SKU.

company_id
string (uuid)

The company this SKU belongs to. Set automatically from your auth context.

namerequired
string

Display name of the product.

brand
string | null

Brand name, if applicable.

categoryrequired
enum
skin_carehair_caremakeupfragranceoral_carefoodbeveragefood_additive

Product category. Determines which regulations apply.

subcategory
string | null

Optional subcategory for more specific classification.

form_factor
string | null

Physical form (cream, serum, spray, etc.).

intended_use
string | null

Product's intended use (e.g. 'daily moisturizer').

description
string | null

Free-text product description.

status
enum
draftactivearchived

Lifecycle status. Draft SKUs cannot be submitted for compliance.

version
number

Auto-incremented version number. Each update bumps this.

tags
string[] | null

Free-form tags for organizing SKUs.

ingredients
Ingredient[]

Array of ingredients in this SKU's formulation.

claims
Claim[]

Marketing claims attached to this SKU.

created_at
string (ISO 8601)

Timestamp of creation.

updated_at
string (ISO 8601)

Timestamp of last modification.

The ComplianceCheck object

Result of checking a SKU against regulations in one or more markets. Contains an overall verdict and individual rule results.

id
string (uuid)

Unique identifier for this check run.

sku_id
string (uuid)

The SKU that was checked.

market
string

Market code (e.g. 'US', 'EU') this check was run against.

overall_status
enum
compliantneeds_reviewnon_compliantuncertainpending

Aggregate compliance verdict.

overall_confidence
number (0-1)

Weighted confidence score across all individual results.

results
CheckResult[]

Array of individual regulation check results.

created_at
string (ISO 8601)

When the check was executed.

The CheckResult object

A single regulation check within a ComplianceCheck. Each result maps one regulation to one ingredient.

id
string (uuid)

Unique identifier for this result.

regulation_id
string (uuid) | null

The regulation that was evaluated.

ingredient_id
string (uuid) | null

The ingredient being checked, if applicable.

status
enum
passfailwarningneeds_review

Outcome of this individual check.

confidence
number (0-1)

How confident the engine is in this result.

check_layer
enum
deterministicconditionalai

Which engine layer produced this result.

violation_detail
string | null

Human-readable explanation of the violation, if any.

citation
string | null

Regulatory citation (e.g. '21 CFR 700.13').

suggested_remediation
string | null

Suggested fix if the check failed or warned.

The Ingredient object

A substance in the master ingredient database. Ingredients are referenced by SKU formulations and linked to regulatory rules.

id
string (uuid)

Unique identifier.

inci_namerequired
string

International Nomenclature of Cosmetic Ingredients name.

cas_number
string | null

Chemical Abstracts Service registry number.

ec_number
string | null

European Community number (EINECS/ELINCS).

e_number
string | null

E number for food additives (e.g. 'E150a').

common_names
string[] | null

Common names in various languages.

synonyms
string[] | null

Alternative INCI or trade names.

category
string | null

Functional category (preservative, colorant, UV filter, etc.).

description
string | null

Plain-language description of the ingredient.

cosing_functions
string[] | null

Functions from the EU CosIng database.

review_status
enum
draftin_reviewapprovedrejected

Admin review status for data quality.

source
string | null

Where this data came from (seed, ai_parse, user, scraper).

The Alert object

A regulatory alert notifying you of compliance risks, regulatory changes, or actions needed on your products.

id
string (uuid)

Unique identifier.

title
string

Short summary of the alert.

description
string

Detailed explanation of the alert.

severity
enum
criticalhighmediumlowinfo

How urgent this alert is.

category
string

Alert category (e.g. regulatory_update, ingredient_ban, deadline).

sku_id
string (uuid) | null

Related SKU, if the alert is product-specific.

market_code
string | null

Related market, if the alert is market-specific.

is_read
boolean

Whether the user has seen this alert.

is_dismissed
boolean

Whether the user has dismissed this alert.

is_resolved
boolean

Whether the underlying issue has been resolved.

deadline
string (ISO 8601) | null

Deadline for action, if applicable.

action_label
string | null

Label for the suggested action button.

action_type
string | null

Type of action (navigate, api_call, etc.).

created_at
string (ISO 8601)

When the alert was generated.

The Task object

A workflow task for the regulatory affairs team. Tasks are organized on a kanban board and can be linked to SKUs and markets.

id
string (uuid)

Unique identifier.

task_number
number

Human-readable sequential number within the company.

titlerequired
string

Task title.

description
string | null

Detailed task description. Supports markdown.

task_typerequired
enum
compliance_reviewlabel_reviewingredient_checkmarket_registrationformula_changeclaim_substantiationregulatory_updatedocumentationsupplier_auditstability_testingsafety_assessmentgeneral

Category of regulatory work.

status
enum
openin_progressreviewblockedcompletedcancelled

Kanban column.

priority
enum
lowmediumhighurgent

Task urgency.

assigned_to_id
string (uuid) | null

User this task is assigned to.

sku_id
string (uuid) | null

Linked SKU, if applicable.

market_code
string | null

Linked market, if applicable.

tags
string[] | null

Free-form tags.

sort_order
number

Position within the kanban column.

The Market object

A regulatory jurisdiction where products can be sold. Each market has its own set of regulations and compliance requirements.

id
string (uuid)

Unique identifier.

code
string
USEUCAMYSGUK

Short code used in API calls.

name
string

Full display name (e.g. 'United States').

region
string

Geographic region (North America, Europe, ASEAN, etc.).

is_active
boolean

Whether this market is currently available.

coming_soon
boolean

Whether this market is planned but not yet available.

regulation_count
number

Number of regulations loaded for this market.

regulatory_body
string | null

Primary regulatory authority (FDA, ECHA, etc.).

flag_emoji
string | null

Country flag emoji.

sub_regions
SubRegion[] | null

Sub-jurisdictions (e.g. California within US).

MCP Tools

Inlet exposes tools via the Model Context Protocol (MCP), allowing AI assistants like Claude, Cursor, and custom agents to interact with the Inlet platform programmatically.

Connecting to the MCP Server

JSON-RPC over HTTP with bearer auth

The MCP endpoint accepts standard JSON-RPC 2.0 requests. Authenticate with your API key.

bash
# MCP JSON-RPC endpoint
POST https://api.inlet.run/api/v1/mcp/rpc
Authorization: Bearer ink_live_your_key_here
Content-Type: application/json

# List available tools
curl -X POST https://api.inlet.run/api/v1/mcp/rpc \
  -H "Authorization: Bearer ink_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/list",
    "id": 1
  }'

Claude Desktop Configuration

Add this to your claude_desktop_config.json to connect Claude Desktop to Inlet:

json
{
  "mcpServers": {
    "inlet": {
      "url": "https://api.inlet.run/api/v1/mcp/rpc",
      "headers": {
        "Authorization": "Bearer ink_live_your_key_here"
      }
    }
  }
}

Cursor Configuration

Add this to your .cursor/mcp.json in your project root:

json
{
  "mcpServers": {
    "inlet": {
      "url": "https://api.inlet.run/api/v1/mcp/rpc",
      "headers": {
        "Authorization": "Bearer ink_live_your_key_here"
      }
    }
  }
}

Using MCP with Navi

When connected via MCP, AI assistants can invoke these tools automatically. Ask "check if my sunscreen is compliant in the EU" and the assistant will call run_compliance_check behind the scenes. You can also invoke tools directly by name for precise control.

Webhooks

Subscribe to real-time event notifications delivered to your endpoints. Get instant updates when compliance checks complete, SKUs change, or new alerts fire.

Supported Events

compliance.completed

A compliance check has finished

sku.created

A new SKU was created

sku.updated

An existing SKU was modified

task.completed

A workflow task was completed

alert.created

A new regulatory alert was generated

submission.status_changed

A submission changed status

Registering a Webhook

Create a webhook endpoint via the API. Specify which events you want to receive and your target URL.

curl -X POST https://api.inlet.run/api/v1/integrations/webhooks \
  -H "Authorization: Bearer ink_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/inlet",
    "events": ["compliance.completed", "sku.created", "alert.created"],
    "secret": "whsec_your_signing_secret"
  }'

# Response:
# {
#   "id": "wh_abc123",
#   "url": "https://your-app.com/webhooks/inlet",
#   "events": ["compliance.completed", "sku.created", "alert.created"],
#   "status": "active",
#   "created_at": "2026-04-12T10:00:00Z"
# }

Signature Verification

Every webhook delivery includes HMAC-SHA256 signature headers so you can verify the payload authenticity.

Delivery Headers

X-Inlet-EventThe event type (e.g. compliance.completed)
X-Inlet-SignatureHMAC-SHA256 hex digest of the payload
X-Inlet-TimestampUnix timestamp of when the event was sent

Verification Example

import hmac
import hashlib
import time

def verify_webhook(payload: bytes, signature: str, timestamp: str, secret: str) -> bool:
    """Verify an Inlet webhook signature."""
    # Reject old events (5 min replay window)
    if abs(time.time() - int(timestamp)) > 300:
        return False

    # Compute expected signature
    message = f"{timestamp}.{payload.decode()}"
    expected = hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, signature)


# FastAPI webhook handler:
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.post("/webhooks/inlet")
async def webhook_handler(request: Request):
    body = await request.body()
    sig = request.headers["x-inlet-signature"]
    ts = request.headers["x-inlet-timestamp"]
    if not verify_webhook(body, sig, ts, "whsec_..."):
        raise HTTPException(status_code=401, detail="Invalid signature")
    event = request.headers["x-inlet-event"]
    # Process event...
    return {"ok": True}

Example Payload

When a compliance check completes, you'll receive a payload like this:

json
{
  "event": "compliance.completed",
  "timestamp": "2026-04-12T10:15:00Z",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "sku_id": "550e8400-e29b-41d4-a716-446655440000",
    "sku_name": "Hydrating Face Cream SPF 30",
    "market": "EU",
    "overall_status": "needs_review",
    "overall_confidence": 0.87,
    "pass_count": 12,
    "fail_count": 0,
    "warning_count": 2,
    "needs_review_count": 1
  }
}

SDKs & Integrations

Official libraries, integrations, and tools for building on Inlet.

Coming Soon

Python SDK

Type-safe Python client with async support. Pydantic models for all request/response types.

python
# pip install inlet-sdk
from inlet import InletClient

client = InletClient(api_key="ink_live_...")
skus = await client.skus.list(status="active")
check = await client.compliance.check(
    sku_id=skus[0].id,
    markets=["US", "EU"]
)
Coming Soon

Node.js SDK

TypeScript-first Node.js client with full type definitions and tree-shaking support.

typescript
// npm install @inlet/sdk
import { Inlet } from "@inlet/sdk";

const inlet = new Inlet({ apiKey: "ink_live_..." });
const skus = await inlet.skus.list({ status: "active" });
const check = await inlet.compliance.check({
  skuId: skus[0].id,
  markets: ["US", "EU"],
});
Available

Slack Integration

@mention Navi in Slack to ask compliance questions. Threaded responses, auto-linked users, BYOK API key support.

Available

Claude Desktop & Cursor

Connect via MCP to use Inlet tools directly in Claude Desktop, Cursor, or any MCP-compatible client.

Ready to integrate?

Create a free account to get your API key and start building with Inlet in minutes.