Everything you need to integrate with Inlet — REST API, MCP tools, webhooks, and SDKs. Automate regulatory compliance checks, manage products, and build custom workflows.
Get up and running with the Inlet API in three steps. All requests are scoped to your company via multi-tenant isolation.
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.
List your SKUs with a single call.
curl https://api.inlet.run/api/v1/skus/ \
-H "Authorization: Bearer ink_live_your_key_here"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"]
}'https://api.inlet.run/api/v1All 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.
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
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.
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.
# 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"
# }# 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.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..."For production sign-in
In production, all sign-ups go through Google OAuth. The flow is a standard OAuth 2.0 authorization code grant.
Get the OAuth URL
POST /auth/google/url returns a url to redirect the user to Google's consent screen.
Exchange the code
After the user approves, Google redirects back with a code. Send it to POST /auth/google/callback.
Receive the JWT
The callback returns an access_token just like the password login flow.
401 UnauthorizedNo valid credentials provided, or the token has expired.
{
"detail": "Could not validate credentials"
}403 ForbiddenValid credentials, but your role lacks permission. For example, only admin/owner can create API keys.
{
"detail": "Insufficient permissions. Required role: admin"
}Security Notes
List endpoints use cursor-based pagination for consistent, performant results even as data changes between pages.
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.
cursorstringOpaque cursor from the previous response's next_cursor. Omit for the first page.
page_sizeintegerNumber of items per page. Default: 50, maximum: 100.
{
"items": [
{ "id": "uuid-1", "name": "Hydrating Face Cream", ... },
{ "id": "uuid-2", "name": "Vitamin C Serum", ... }
],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0xMCIsImlkIjoiYWJjMTIzIn0=",
"has_more": true,
"total": 247
}# 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 falseAdmin endpoints (/ingredients/admin/all,/regulations/admin/all) use traditional offset-based pagination.
pageintegerPage number (1-indexed). Default: 1.
page_sizeintegerItems per page. Default: 50, maximum: 200.
{
"items": [...],
"meta": {
"total": 1247,
"page": 3,
"page_size": 50,
"total_pages": 25
}
}The Inlet API uses standard HTTP status codes. All errors return a JSON body with a detail field.
200OKRequest succeeded. Response body contains the result.
201CreatedResource created successfully. Response body contains the new resource.
400Bad RequestThe request was malformed or missing required fields.
401UnauthorizedNo valid authentication credentials provided.
403ForbiddenValid credentials, but insufficient permissions for this action.
404Not FoundThe requested resource does not exist or is not accessible.
409ConflictThe request conflicts with existing state (e.g. duplicate name).
422Validation ErrorRequest body failed Pydantic validation. See detail array for field-level errors.
429Too Many RequestsRate limit exceeded. Check Retry-After header.
500Internal Server ErrorSomething went wrong on our end. Retry with exponential backoff.
Most errors return a simple string detail.
{
"detail": "SKU not found"
}When the request body fails Pydantic validation, the detail field is an array of field-level errors with location, message, and type.
{
"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"
}
]
}When rate limited, check the Retry-After header (in seconds) before retrying.
// HTTP 429 Too Many Requests
// Retry-After: 30
{
"detail": "Rate limit exceeded. Try again in 30 seconds."
}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()95 endpoints across 15 resource groups. Base URL: https://api.inlet.run/api/v1
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 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"
}'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 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 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 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"
}'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.
Reference documentation for the key API objects. Expand any object to see its full field schema and an example JSON representation.
Represents a product (Stock Keeping Unit) with its formulation, claims, and lifecycle state.
idstring (uuid)Unique identifier for the SKU.
company_idstring (uuid)The company this SKU belongs to. Set automatically from your auth context.
namerequiredstringDisplay name of the product.
brandstring | nullBrand name, if applicable.
categoryrequiredenumProduct category. Determines which regulations apply.
subcategorystring | nullOptional subcategory for more specific classification.
form_factorstring | nullPhysical form (cream, serum, spray, etc.).
intended_usestring | nullProduct's intended use (e.g. 'daily moisturizer').
descriptionstring | nullFree-text product description.
statusenumLifecycle status. Draft SKUs cannot be submitted for compliance.
versionnumberAuto-incremented version number. Each update bumps this.
tagsstring[] | nullFree-form tags for organizing SKUs.
ingredientsIngredient[]Array of ingredients in this SKU's formulation.
claimsClaim[]Marketing claims attached to this SKU.
created_atstring (ISO 8601)Timestamp of creation.
updated_atstring (ISO 8601)Timestamp of last modification.
Result of checking a SKU against regulations in one or more markets. Contains an overall verdict and individual rule results.
idstring (uuid)Unique identifier for this check run.
sku_idstring (uuid)The SKU that was checked.
marketstringMarket code (e.g. 'US', 'EU') this check was run against.
overall_statusenumAggregate compliance verdict.
overall_confidencenumber (0-1)Weighted confidence score across all individual results.
resultsCheckResult[]Array of individual regulation check results.
created_atstring (ISO 8601)When the check was executed.
A single regulation check within a ComplianceCheck. Each result maps one regulation to one ingredient.
idstring (uuid)Unique identifier for this result.
regulation_idstring (uuid) | nullThe regulation that was evaluated.
ingredient_idstring (uuid) | nullThe ingredient being checked, if applicable.
statusenumOutcome of this individual check.
confidencenumber (0-1)How confident the engine is in this result.
check_layerenumWhich engine layer produced this result.
violation_detailstring | nullHuman-readable explanation of the violation, if any.
citationstring | nullRegulatory citation (e.g. '21 CFR 700.13').
suggested_remediationstring | nullSuggested fix if the check failed or warned.
A substance in the master ingredient database. Ingredients are referenced by SKU formulations and linked to regulatory rules.
idstring (uuid)Unique identifier.
inci_namerequiredstringInternational Nomenclature of Cosmetic Ingredients name.
cas_numberstring | nullChemical Abstracts Service registry number.
ec_numberstring | nullEuropean Community number (EINECS/ELINCS).
e_numberstring | nullE number for food additives (e.g. 'E150a').
common_namesstring[] | nullCommon names in various languages.
synonymsstring[] | nullAlternative INCI or trade names.
categorystring | nullFunctional category (preservative, colorant, UV filter, etc.).
descriptionstring | nullPlain-language description of the ingredient.
cosing_functionsstring[] | nullFunctions from the EU CosIng database.
review_statusenumAdmin review status for data quality.
sourcestring | nullWhere this data came from (seed, ai_parse, user, scraper).
A regulatory alert notifying you of compliance risks, regulatory changes, or actions needed on your products.
idstring (uuid)Unique identifier.
titlestringShort summary of the alert.
descriptionstringDetailed explanation of the alert.
severityenumHow urgent this alert is.
categorystringAlert category (e.g. regulatory_update, ingredient_ban, deadline).
sku_idstring (uuid) | nullRelated SKU, if the alert is product-specific.
market_codestring | nullRelated market, if the alert is market-specific.
is_readbooleanWhether the user has seen this alert.
is_dismissedbooleanWhether the user has dismissed this alert.
is_resolvedbooleanWhether the underlying issue has been resolved.
deadlinestring (ISO 8601) | nullDeadline for action, if applicable.
action_labelstring | nullLabel for the suggested action button.
action_typestring | nullType of action (navigate, api_call, etc.).
created_atstring (ISO 8601)When the alert was generated.
A workflow task for the regulatory affairs team. Tasks are organized on a kanban board and can be linked to SKUs and markets.
idstring (uuid)Unique identifier.
task_numbernumberHuman-readable sequential number within the company.
titlerequiredstringTask title.
descriptionstring | nullDetailed task description. Supports markdown.
task_typerequiredenumCategory of regulatory work.
statusenumKanban column.
priorityenumTask urgency.
assigned_to_idstring (uuid) | nullUser this task is assigned to.
sku_idstring (uuid) | nullLinked SKU, if applicable.
market_codestring | nullLinked market, if applicable.
tagsstring[] | nullFree-form tags.
sort_ordernumberPosition within the kanban column.
A regulatory jurisdiction where products can be sold. Each market has its own set of regulations and compliance requirements.
idstring (uuid)Unique identifier.
codestringShort code used in API calls.
namestringFull display name (e.g. 'United States').
regionstringGeographic region (North America, Europe, ASEAN, etc.).
is_activebooleanWhether this market is currently available.
coming_soonbooleanWhether this market is planned but not yet available.
regulation_countnumberNumber of regulations loaded for this market.
regulatory_bodystring | nullPrimary regulatory authority (FDA, ECHA, etc.).
flag_emojistring | nullCountry flag emoji.
sub_regionsSubRegion[] | nullSub-jurisdictions (e.g. California within US).
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.
JSON-RPC over HTTP with bearer auth
The MCP endpoint accepts standard JSON-RPC 2.0 requests. Authenticate with your API key.
# 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
}'Add this to your claude_desktop_config.json to connect Claude Desktop to Inlet:
{
"mcpServers": {
"inlet": {
"url": "https://api.inlet.run/api/v1/mcp/rpc",
"headers": {
"Authorization": "Bearer ink_live_your_key_here"
}
}
}
}Add this to your .cursor/mcp.json in your project root:
{
"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.
Subscribe to real-time event notifications delivered to your endpoints. Get instant updates when compliance checks complete, SKUs change, or new alerts fire.
compliance.completedA compliance check has finished
sku.createdA new SKU was created
sku.updatedAn existing SKU was modified
task.completedA workflow task was completed
alert.createdA new regulatory alert was generated
submission.status_changedA submission changed status
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"
# }Every webhook delivery includes HMAC-SHA256 signature headers so you can verify the payload authenticity.
X-Inlet-EventThe event type (e.g. compliance.completed)X-Inlet-SignatureHMAC-SHA256 hex digest of the payloadX-Inlet-TimestampUnix timestamp of when the event was sentimport 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}When a compliance check completes, you'll receive a payload like this:
{
"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
}
}Official libraries, integrations, and tools for building on Inlet.
Type-safe Python client with async support. Pydantic models for all request/response types.
# 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"]
)TypeScript-first Node.js client with full type definitions and tree-shaking support.
// 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"],
});@mention Navi in Slack to ask compliance questions. Threaded responses, auto-linked users, BYOK API key support.
Connect via MCP to use Inlet tools directly in Claude Desktop, Cursor, or any MCP-compatible client.
Create a free account to get your API key and start building with Inlet in minutes.