API Reference
Technical documentation for integrating with Aether’s APIs.
Overview
Aether provides multiple API endpoints for integration:
- GraphQL API - Primary API for all data operations
- REST API - For webhook receivers and simple integrations
- Webhooks - Receive notifications for events
Authentication
All API requests require authentication via JWT tokens.
Authentication Flow
1. User authenticates (magic link, OAuth, SSO)
2. Server returns JWT access token
3. Client includes token in Authorization header
4. Client specifies Workspace in X-Aether-Workspace header
5. Server validates and processes requestHeaders
| Header | Required | Description |
|---|---|---|
Authorization |
Bearer <jwt-token> |
|
X-Aether-Workspace |
ws_<workspace-uuid> for workspace binding |
|
Content-Type |
application/json for most requests |
|
Idempotency-Key |
For mutation retries (recommended) | |
X-Aether-Client |
Client identifier for tracking |
Token Management
- Access Token TTL: Configurable (default: 1 hour)
- Refresh Token TTL: Configurable (default: 7 days)
- Token Type: JWT with claims for account ID, email, etc.
- Revocation: Tokens can be revoked (logout)
Rate Limits
Aether implements comprehensive rate limiting to protect the platform.
Scope Hierarchy
Rate limits are applied at multiple scopes. The strictest match wins:
Request
↓
Per-Session (access token) → Smallest
↓
Per-Account (your identity) → Primary authenticated cap
↓
Per-Workspace (tenant-scoped) → Automation cap
↓
Per-IP (source address) → Backstop (MUST be ≥ Per-Workspace)
↓
Per-Endpoint Class → Feature-specific limitsWhy this hierarchy?
- Prevents a single leaked token from overwhelming the system (Per-Session)
- Prevents multiplying quota across multiple Free Workspaces (Per-Account below Per-Workspace)
- Ensures single-IP users never trip IP before Workspace (Per-IP above Per-Workspace)
Default Per-Plan Ceilings
Per-Account (Authenticated)
| Window | Free | Entrepreneur | Enterprise |
|---|---|---|---|
| Per-second burst | 10 | 30 | 100 |
| Per-minute | 100 | 600 | 6,000 |
| Per-hour | 1,000 | 6,000 | 60,000 |
| Per-day | 10,000 | 60,000 | 600,000 (soft) |
Per-Workspace (Authenticated, Secondary)
| Window | Free | Entrepreneur | Enterprise |
|---|---|---|---|
| Per-minute | 100 | 1,000 | 10,000 |
| Per-hour | 1,000 | 10,000 | 100,000 |
| Per-day | 10,000 | 100,000 | 1,000,000 (soft) |
Per-IP (Authed + Anonymous Backstop)
| Window | Free-equivalent | Entrepreneur-equivalent | Enterprise-equivalent |
|---|---|---|---|
| Per-second burst | 20 | 100 | 1,000 |
| Per-minute | 200 | 1,200 | 12,000 |
| Per-hour | 2,000 | 12,000 | 120,000 |
| Per-day | 20,000 | 120,000 | 1,200,000 (soft) |
Per-Session (Safety Net)
| Window | All Plans |
|---|---|
| Per-minute | 200 |
| Per-hour | 2,000 |
Mutation Window (Separate Bucket)
| Window | Free | Entrepreneur | Enterprise |
|---|---|---|---|
| Per-minute mutations | 30 | 200 | 2,000 |
| Per-hour mutations | 300 | 2,000 | 20,000 |
Endpoint Class Limits
Specific high-cost endpoints have their own quotas:
| Endpoint Class | Free | Entrepreneur | Enterprise |
|---|---|---|---|
| INCI Ingredient Import | 10/hour | 100/hour | 1,000/hour |
| Marketplace Product Sync | 60/hour | 600/hour | |
| Compliance Doc Generation | 5/hour | 50/hour | 500/hour |
| Audit Log Export | 1/hour | 10/hour | |
| Workspace Export | 1/week | 1/week | 5/week |
Rate Limit Headers
Every API response includes:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1736500800
X-RateLimit-Scope: account| Header | Description |
|---|---|
X-RateLimit-Limit |
Ceiling for the matched scope |
X-RateLimit-Remaining |
Tokens left in current window |
X-RateLimit-Reset |
Unix timestamp when window resets |
X-RateLimit-Scope |
Which scope matched (account, workspace, ip, session) |
Retry-After |
Seconds until retry (on 429 only) |
Error Responses
429 Too Many Requests
{
"errors": [{
"message": "Rate limit exceeded for scope 'account'. Try again in 12 seconds.",
"extensions": {
"code": "rate_limited",
"scope": "account",
"scopeId": "acc_...",
"retryAfterSeconds": 12,
"resetAt": "2026-07-07T13:50:00Z",
"limit": 100,
"window": "minute"
}
}]
}423 Locked (Workspace Suspended)
{
"errors": [{
"message": "Workspace is suspended. Contact the Workspace Owner or Aether support.",
"extensions": {
"code": "workspace_suspended",
"workspaceStatus": "Suspended"
}
}]
}Bypass Lanes
Some traffic is NOT subject to user-scope limits:
| Lane | Identification | Limit |
|---|---|---|
| Stripe Webhooks | HMAC signature verification | 100/sec per-event-type |
| Shopify Webhooks | HMAC signature verification | 100/sec per-workspace |
| OAuth Callbacks | Path | 10/sec per-IP |
| Health Checks | Path (/healthz, /livez, /readyz) |
Unlimited |
| Metrics Scrape | Path + ops network | Unlimited |
GraphQL API
Aether’s primary API is GraphQL-based, providing flexible querying for all data.
Schema Overview
The GraphQL schema includes:
# Root types
type Query {
# Product Development
ingredient(id: ID!): Ingredient
ingredients(filter: IngredientFilter, pagination: Pagination): IngredientConnection
vessel(id: ID!): Vessel
vessels(filter: VesselFilter, pagination: Pagination): VesselConnection
mixture(id: ID!): Mixture
mixtures(filter: MixtureFilter, pagination: Pagination): MixtureConnection
productTemplate(id: ID!): ProductTemplate
productTemplates(filter: ProductTemplateFilter, pagination: Pagination): ProductTemplateConnection
product(id: ID!): Product
products(filter: ProductFilter, pagination: Pagination): ProductConnection
# Inventory
stockItem(id: ID!): StockItem
stockItems(filter: StockItemFilter, pagination: Pagination): StockItemConnection
supplier(id: ID!): Supplier
suppliers(filter: SupplierFilter, pagination: Pagination): SupplierConnection
# Compliance
sds(id: ID!): SafetyDataSheet
allergenSheet(id: ID!): AllergenSheet
# Ecommerce
shopifyProduct(id: ID!): ShopifyProduct
orders(filter: OrderFilter, pagination: Pagination): OrderConnection
# Platform
workspace(id: ID!): Workspace
account: Account
seats: [Seat!]!
}
type Mutation {
# Product Development
createIngredient(input: CreateIngredientInput!): Ingredient!
updateIngredient(id: ID!, input: UpdateIngredientInput!): Ingredient!
deleteIngredient(id: ID!): Boolean!
createVessel(input: CreateVesselInput!): Vessel!
updateVessel(id: ID!, input: UpdateVesselInput!): Vessel!
deleteVessel(id: ID!): Boolean!
createMixture(input: CreateMixtureInput!): Mixture!
updateMixture(id: ID!, input: UpdateMixtureInput!): Mixture!
deleteMixture(id: ID!): Boolean!
createProductTemplate(input: CreateProductTemplateInput!): ProductTemplate!
updateProductTemplate(id: ID!, input: UpdateProductTemplateInput!): ProductTemplate!
deleteProductTemplate(id: ID!): Boolean!
createProduct(input: CreateProductInput!): Product!
updateProduct(id: ID!, input: UpdateProductInput!): Product!
deleteProduct(id: ID!): Boolean!
# Inventory
createStockItem(input: CreateStockItemInput!): StockItem!
updateStockItem(id: ID!, input: UpdateStockItemInput!): StockItem!
adjustStock(id: ID!, quantity: Float!, reason: String): StockItem!
# Compliance
generateSDS(productId: ID!): SafetyDataSheet!
generateAllergenSheet(productId: ID!): AllergenSheet!
generateIngredientList(productId: ID!, targetMarket: Market!): IngredientList!
# Ecommerce
syncToShopify(productId: ID!): ShopifySyncResult!
importOrders(channel: Channel!, since: DateTime): ImportResult!
}
type Subscription {
# Real-time updates
ingredientUpdated: Ingredient!
stockLevelChanged: StockItem!
orderCreated: Order!
}Query Complexity Limits
To prevent expensive queries, Aether implements a 5-layer defense:
Layer 1: Static Query Cost Ceiling (Parse Time)
Every field has a cost. Queries over the limit are rejected before execution.
| Plan | Max Single-Query Cost |
|---|---|
| Free | 1,000 |
| Entrepreneur | 10,000 |
| Enterprise | 100,000 |
Cost Factors:
| Factor | Weight | Notes |
|---|---|---|
| Scalar field | 1 | Leaf value |
| Object field | 2 | One fan-out |
| List field | 5 × estimated cardinality | Uses first/last or defaults |
| Nested traversal | × depth multiplier | 1.5^depth for recursive types |
| Mutation | × 10 base | Heavy operations |
| Heavy mutation | × override | See mutation cost table |
| Subscription | × 50 initial, × 10/event | Long-lived |
Layer 2: Depth Limit (Parse Time)
| Plan | Max Depth | Hard Cap |
|---|---|---|
| Free | 7 | 20 |
| Entrepreneur | 10 | 20 |
| Enterprise | 15 | 20 |
Layer 3: Aliasing & Batch Defense (Parse Time)
Aliased repetitions count as lists. Max batch operations:
| Plan | Max Operations per Batch |
|---|---|
| Free | 1 (batch disabled) |
| Entrepreneur | 5 |
| Enterprise | 25 |
| Hard cap | 50 |
Layer 4: Cost-Based Token Bucket (Execution Time)
Cumulative cost per time window:
| Window | Free | Entrepreneur | Enterprise |
|---|---|---|---|
| Per-minute | 5,000 | 50,000 | 500,000 |
| Per-hour | 50,000 | 500,000 | 5,000,000 |
| Per-day | 500,000 | 5,000,000 | 50,000,000 (soft) |
Layer 5: Result-Size Cap (Post-Execution)
| Plan | Max Response Size |
|---|---|
| Free | 1 MB |
| Entrepreneur | 10 MB |
| Enterprise | 100 MB |
| Hard cap | 250 MB |
Responses exceeding the cap are truncated with a warning.
Mutation Cost Overrides
Some mutations have higher costs:
| Mutation | Cost Override | Reason |
|---|---|---|
| INCI import (single) | 50 | External service call |
| INCI import (composite) | 200 + 50 per component | Recursive resolution |
| OCR scan submit | 500 | Async job + vendor call |
| Marketplace product push | 100 | API call |
| Marketplace sync all | 1,000 | Bulk sync |
| SDS/Allergen/Label generate | 200 each | Document generation |
| Audit export | 100 | Export job |
| Workspace export | 500 | GDPR export |
| Seat invite/revoke | 30 | Triggers payments + email |
| Workspace delete | 5,000 | Cascading delete |
| Standard mutations | 10 | Default |
GraphQL Endpoint
POST https://api.aether.com/graphqlExample Query
query GetProduct($id: ID!) {
product(id: $id) {
id
name
description
vessel {
id
name
volume
}
components {
ingredient {
id
name
inciName
}
percentage
}
cogs {
total
breakdown {
item
cost
}
}
}
}Example Mutation
mutation CreateIngredient($input: CreateIngredientInput!) {
createIngredient(input: $input) {
id
name
inciName
casNumber
density
cost
}
}REST API
For simpler integrations and webhook receivers.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/healthz |
Health check |
| GET | /api/livez |
Liveness check |
| GET | /api/readyz |
Readiness check (pings DB) |
| POST | /webhooks/stripe |
Stripe webhook receiver |
| POST | /webhooks/shopify |
Shopify webhook receiver |
Stripe Webhook
Receive Stripe events for subscription management.
Endpoint: POST /webhooks/stripe
Headers:
Stripe-Signature: <signature>
Content-Type: application/jsonSupported Events:
invoice.payment_succeededinvoice.payment_failedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedcheckout.session.completed
Shopify Webhook
Receive Shopify events for order and product updates.
Endpoint: POST /webhooks/shopify
Headers:
X-Shopify-Hmac-Sha256: <signature>
X-Shopify-Shop-Domain: <shop>.myshopify.com
Content-Type: application/jsonSupported Topics:
orders/createorders/updatedorders/paidorders/fulfilledproducts/createproducts/updateproducts/deleteinventory_levels/update
Webhooks
Aether can send webhooks to your endpoints for various events.
Configuration
Configure webhooks in your Workspace settings:
- URL - Your endpoint URL
- Secret - Secret key for HMAC verification
- Events - Which events to subscribe to
- Active - Enable/disable the webhook
Outbound Webhooks
Aether sends webhooks for:
| Event | Description | Payload |
|---|---|---|
product.created |
Product created in Aether | Product data |
product.updated |
Product updated in Aether | Product data |
product.deleted |
Product deleted in Aether | Product ID |
stock.low |
Stock item reached low threshold | StockItem + threshold |
stock.empty |
Stock item out of stock | StockItem |
order.created |
Order imported from channel | Order data |
compliance.issue |
Compliance issue detected | Issue details |
Webhook Format
{
"event": "product.created",
"timestamp": "2026-07-09T10:00:00Z",
"workspaceId": "ws_...",
"data": {
"id": "prod_...",
"name": "Lavender Body Spray",
"...": "..."
},
"signature": "sha256=..."
}Verification
Verify webhook signatures:
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
const received = signature.split('=')[1];
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}SDKs
JavaScript/TypeScript SDK
Coming soon - Official SDK for browser and Node.js.
Rust SDK
Coming soon - Official SDK for Rust backend integrations.
OpenAPI Spec
The complete API specification is available for generating client libraries:
# Swagger/OpenAPI 3.0
openapi: 3.0.0
info:
title: Aether API
version: 1.0.0
servers:
- url: https://api.aether.com
description: Production
- url: https://staging-api.aether.com
description: StagingBest Practices
Rate Limiting
- Honor
Retry-After- Always wait the specified time before retrying - Exponential Backoff - Use exponential backoff with jitter for retries
- Idempotency Keys - Always include for mutations to prevent double-execution
- Monitor Headers - Track
X-RateLimit-Remainingand back off proactively when < 20% - Batch Requests - Combine multiple operations into one request when possible
Authentication
- Store Tokens Securely - Never commit tokens to version control
- Refresh Proactively - Refresh tokens before they expire
- Handle 401s - Automatically refresh and retry on token expiration
- Per-Request Workspace - Specify the Workspace header for each request
Error Handling
- 429 Retry - Retry after
Retry-Afteror exponential backoff - 403 Check - Verify permissions and plan limits
- 423 Handle - Workspace is suspended, limited functionality available
- 400 Validate - Check query/mutation syntax
Testing
- Sandbox Environment - Use staging for development
- Test Tokens - Use test accounts and workspaces
- Mock Data - Test with realistic data volumes
- Rate Limit Testing - Verify your handling of rate limits
Support
API Support
- Entrepreneur Plan - Email support for API questions
- Enterprise Plan - Priority support with SLA
- Custom Plans - Dedicated API support available
Status Page
Check API status at: https://status.aether.com
Changelog
API changes are documented in the API Changelog.
Next Steps
- Review Platform Concepts for understanding workspaces
- Explore Product Development for creating products
- View Rate Limits in Detail for integration planning