REST API & integrations

Integrate AVIA ERP through the portal REST API, the public endpoints and synchronisation with the marketing site.

Architecture

The business API is exposed by avcore on the portal portal.aviaerp.com. The marketing site aviaerp.com proxies the public /api/v1/public/* routes to the portal to avoid browser-side CORS issues.

URLs
Portail ERP (API principale) : https://portal.aviaerp.com/api/v1/
Landing (proxy public)       : https://aviaerp.com/api/v1/public/
Landing (routes internes)    : https://aviaerp.com/api/erp-sync
                               https://aviaerp.com/api/paddle/webhook

Authentication

Authenticated calls use a JWT obtained via POST /api/v1/auth/login (email + password or SSO depending on the tenant). Include the token in the header:

HTTP
Authorization: Bearer <access_token>
X-Tenant: erppro
Content-Type: application/json

Server-to-server integrations (landing sync, internal SaaS keys) use X-API-Key with the shared key SAAS_INTERNAL_API_KEY / LANDING_SYNC_API_KEY configured on both sides.

API keys

For a third-party integration (not a portal session), create a key from Settings → API keys — reserved for admin / super-admin roles. The secret is shown once, at creation time only:

HTTP
Authorization: Bearer avia_live_<prefixe>_<secret>
X-Tenant-ID: <votre_tenant>

A key is scoped to one tenant (no fallback tenant for a third-party caller) and carries scopes — one pair per purchased module, module:read / module:write (write also covers read), plus rules and webhooks for those two surfaces outside the billed modules. Rights actually granted are the narrower of the key's scopes and its holder's roles — a scope only restricts further, it never grants more than the holder already has. Expiration is mandatory, up to 1 year; a revoked or expired key fails immediately (cle_api_invalide). A key is rejected outright if the request carries an Origin header — never usable from a browser.

Rate limit is per key (not per user), headers X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset on every response, default 600 req/min.

Public endpoints

Examples of endpoints reachable without a user session (depending on deployment):

RouteUsage
GET /api/v1/public/subscription-plansPricing plans shown on /tarifs
POST /api/contactContact form (Next.js landing site)
POST /api/demoDemo request

ERP → landing synchronisation

The portal periodically pushes tenant, plans and metadata to POST /api/erp-sync on the landing site. Configure the URL under Settings → Portal access:

Configuration
URL sync (prod)  : https://aviaerp.com/api/erp-sync
URL sync (local)   : http://localhost:3030/api/erp-sync
Health check       : GET .../api/erp-sync/health

The LANDING_SYNC_API_KEY must be identical on the portal and in the landing's .env.production.

Webhooks

Paddle — configure the webhook to https://aviaerp.com/api/paddle/webhook for transaction and subscription events.

Business events — subscribe from Settings → Webhooks on the portal to any of the ~27 cataloged events (document creation, status change, approval steps — the full list is queryable at GET /settings/webhooks/events and documented in the webhooks: section of the OpenAPI contract). Each delivery is signed and carries:

HTTP
X-Avia-Event: invoice.created
X-Avia-Delivery-Id: 6f1e2c3a-...      # stable meme apres une relivraison manuelle
X-Avia-Timestamp: 1755792000
X-Avia-Webhook-Version: 2026-08-19
X-Avia-Signature: sha256=<hmac>       # deux valeurs separees par une virgule pendant une rotation

The signature is an HMAC-SHA256 over <timestamp>.<raw body> with the subscription's secret — verify it and reject anything older than a 5-minute window. Regenerate the secret any time from the subscription screen (Régénérer le secret): the old one keeps signing alongside the new one for 24h (double signature above), so in-flight deliveries never fail during a rotation. A failed delivery is retried with a quadratic backoff and can be replayed manually from the delivery log (POST .../webhooks/{id}/deliveries/{did}/retry).

TypeScript SDK

@aviaerp/sdk (sdk/typescript/ in the repository) wraps the generated types (openapi-typescript) with a typed HTTP client (openapi-fetch): automatic retry on 429 respecting Retry-After, typed {code, params, message} errors, filter helpers matching the generic CRUD syntax, iterable pagination, and a verifyWebhookSignature helper (Web Crypto, no server-side secret exposed to a browser). It is currently a private workspace package, not yet published to a registry — consumed today by copying the package.

TypeScript
import { createAviaClient } from "@aviaerp/sdk"

const client = createAviaClient({
  baseUrl: "https://portal.aviaerp.com",
  apiKey: process.env.AVIA_API_KEY!,
  tenantId: "erppro",
})

const { data, error } = await client.GET("/api/v1/sales-orders", {
  params: { query: { status: "eq.confirme", page_size: 20 } },
})

Full reference (OpenAPI)

Every route above the three shown here — 1000+ paths, generated from the code, never hand-written — is browsable module by module, searchable, with parameters, response schema and a request example for each one:

Open the interactive reference

GET /api/v1/openapi.json