Skip to content

Identity & Registration API

The Identity API is how a business gets onto Invora and how a platform manages the businesses underneath it. It covers two public services:

  • PublicRegistrationService — self-service registration and onboarding for a newly signed-up user (complete profile, pick a plan, track onboarding progress).
  • ConnectedBusinessService — create and manage sub-businesses (Connected Businesses) under a platform account.

Becoming a platform requires an application that Invora staff review. There is no public RPC to submit that application through this API; submission and review happen through the Invora dashboard.

Base URLs & auth

Environment Base URL
Production https://gateway.invora.app
Staging https://stg-gateway.invora.app

Every call needs a bearer token. Obtain one with the client-credentials grant and export it as $TOKEN:

TOKEN=$(curl -s -X POST https://auth.invora.app/oauth/v2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET" \
  --data-urlencode "scope=openid urn:zitadel:iam:org:project:id:372376660185448530:aud urn:zitadel:iam:user:resourceowner" \
  | jq -r '.access_token')

All endpoints are gRPC services exposed over JSON transcoding: send camelCase JSON bodies, receive camelCase JSON. Each RPC is gated by a scope (see the RPC reference). See Authentication for tokens, scopes, and tenancy, and Error handling for status codes.

Core concepts

Organization types

Every organization on Invora is one of three types (OrgType):

Type Enum Description
Business ORG_TYPE_BUSINESS A standalone company using Invora for e-invoicing, billing, or both.
Platform ORG_TYPE_PLATFORM A company that also manages other businesses underneath it (reseller, SaaS provider, franchisor, aggregator).
Connected Business ORG_TYPE_CONNECTED_BUSINESS A sub-business created and managed by a Platform. Shares the platform's billing relationship — usage rolls up to the parent.

A Business can apply to become a Platform. A Connected Business is always created by its parent Platform.

Capabilities

When registering, a business declares which Invora capabilities it wants (BusinessCapability):

Capability Enum What it enables
E-Invoicing BUSINESS_CAPABILITY_EINVOICING Create, validate, and submit electronic invoices compliant with local regulations (ZATCA, Peppol, etc.).
Billing BUSINESS_CAPABILITY_BILLING Subscriptions, usage metering, recurring invoicing, and payment collection — see Billing.

A business can enable one or both. Capabilities can be changed later.

Onboarding checklist

Onboarding progress is tracked as a checklist. Each OnboardingStep has a key, a human-readable title, and a completed flag; the OnboardingChecklist also reports a completionRatio (0.0–1.0) so you can render a progress bar. The checklist is returned from most registration endpoints, so your UI always has the latest state without a separate fetch.

Registration & onboarding

A new business goes through these steps. Account creation happens in Invora's auth system (hosted login or your embedded flow) and yields a token but no business profile yet — everything after that is this API.

flowchart TD
  A["Create account<br/>(auth system — outside this API)"]
  B["Complete profile<br/>POST /registration/complete-profile<br/>(creates org + free trial)"]
  C["List available plans<br/>GET /registration/available-plans"]
  D["Select plan<br/>POST /registration/select-plan"]
  E["Business details<br/>PUT /registration/business-details<br/>(optional, progressive)"]
  F["Active business"]
  A --> B --> C --> D --> E --> F

Complete business profile

The first call a new user makes. Creates the organization, activates a free trial, and returns the machine-to-machine API credentials. Pass capabilities using their full enum names.

curl -X POST https://gateway.invora.app/api/identity/v2/registration/complete-profile \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "businessName": "Acme Trading Co.",
    "country": "SA",
    "capabilities": ["BUSINESS_CAPABILITY_EINVOICING", "BUSINESS_CAPABILITY_BILLING"]
  }'
Response
{
  "tenantId": "317842069254438913",
  "businessName": "Acme Trading Co.",
  "checklist": {
    "steps": [
      { "key": "complete_profile", "title": "Complete business profile", "completed": true },
      { "key": "select_plan", "title": "Choose a plan", "completed": false },
      { "key": "business_details", "title": "Add tax & address details", "completed": false }
    ],
    "completionRatio": 0.33
  },
  "clientId": "317842069254438914@acme",
  "clientSecret": "Hh8s...redacted...kQ"
}

Warning

clientSecret is returned only once, at creation. Store it in a secure vault immediately — it cannot be retrieved again. If lost, recreate credentials with RotateCredentials (Connected Businesses) or your account settings.

tenantId is your organization's identifier across every Invora API. The clientId/clientSecret pair is for server-to-server access (background jobs, integrations, webhooks).

List available plans

A curated, public subset of the billing catalog that an authenticated user can see before their organization exists (a freshly registered user has no billing roles yet). Optional currency and capabilities filters map to query parameters.

curl -X GET "https://gateway.invora.app/api/identity/v2/registration/available-plans?currency=CURRENCY_ENUM_SAR&capabilities=BUSINESS_CAPABILITY_EINVOICING" \
  -H "Authorization: Bearer $TOKEN"
Response
{
  "plans": [
    {
      "id": "01963fa0-82c4-7000-9e93-50285f8f55c6",
      "code": "starter",
      "name": "Starter",
      "description": "For small businesses getting started with e-invoicing.",
      "amountCents": 0,
      "amountCurrency": "CURRENCY_ENUM_SAR",
      "interval": "PLAN_INTERVAL_MONTHLY",
      "trialPeriodDays": 14,
      "features": [
        { "label": "Up to 100 invoices/month", "detail": "", "included": true },
        { "label": "Connected businesses", "detail": "Platform tier only", "included": false }
      ],
      "recommended": false,
      "ctaLabel": ""
    }
  ]
}

Use a plan's id as the planCode in the next step.

Select a plan

Subscribe the business to a billing plan. Returns the new subscriptionId and the updated checklist.

curl -X POST https://gateway.invora.app/api/identity/v2/registration/select-plan \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "planCode": "01963fa0-82c4-7000-9e93-50285f8f55c6" }'
Response
{
  "subscriptionId": "01963f90-71b3-7000-8e91-4f073d6d33a4",
  "checklist": {
    "steps": [
      { "key": "complete_profile", "title": "Complete business profile", "completed": true },
      { "key": "select_plan", "title": "Choose a plan", "completed": true },
      { "key": "business_details", "title": "Add tax & address details", "completed": false }
    ],
    "completionRatio": 0.67
  }
}

Update business details (optional)

Tax, address, currency, and timezone. Supports progressive profiling — call it during onboarding or defer it to a later settings flow. Returns the updated checklist.

curl -X PUT https://gateway.invora.app/api/identity/v2/registration/business-details \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "taxId": "300000000000003",
    "addressLine": "King Fahd Road",
    "city": "Riyadh",
    "postalCode": "11564",
    "country": "SA",
    "currency": "SAR",
    "timezone": "Asia/Riyadh"
  }'
Response
{
  "checklist": {
    "steps": [
      { "key": "complete_profile", "title": "Complete business profile", "completed": true },
      { "key": "select_plan", "title": "Choose a plan", "completed": true },
      { "key": "business_details", "title": "Add tax & address details", "completed": true }
    ],
    "completionRatio": 1.0
  }
}

Get onboarding status

Check where the business stands at any time. Returns the tenant ID, organization type, and the full checklist.

curl -X GET https://gateway.invora.app/api/identity/v2/registration/onboarding-status \
  -H "Authorization: Bearer $TOKEN"
Response
{
  "tenantId": "317842069254438913",
  "orgType": "ORG_TYPE_BUSINESS",
  "checklist": {
    "steps": [
      { "key": "complete_profile", "title": "Complete business profile", "completed": true },
      { "key": "select_plan", "title": "Choose a plan", "completed": true },
      { "key": "business_details", "title": "Add tax & address details", "completed": true }
    ],
    "completionRatio": 1.0
  }
}

For a complete walkthrough from registration to a frozen invoice, see the Quickstart.

Connected businesses

Connected Businesses are the foundation of Invora's platform and reseller model. If your organization manages other businesses — as a reseller, franchise network, SaaS platform, or aggregator — you create each of those as a Connected Business under your account.

  • Centralized billing. Usage from all Connected Businesses rolls up to the parent platform. The parent pays one bill; sub-businesses need no separate payment method.
  • Centralized management. The parent creates, updates, suspends, reactivates, and deletes Connected Businesses, and rotates their credentials, through this service.
  • Isolated data. Each Connected Business is a fully separate tenant with its own users, documents, and configuration. The parent manages the lifecycle; data stays isolated. See Multi-tenancy for the tenant model and cross-tenant visibility rules.

If you issue invoices on behalf of the businesses underneath you, review the regulatory implications in the marketplace & intermediary invoicing study.

The capability gate

Creating a Connected Business requires a plan that includes the connected business capability — a billing entitlement, not a numeric quota. If your current plan does not grant it, CreateConnectedBusiness fails with PERMISSION_DENIED (HTTP 403). Upgrade your plan to a tier that includes the capability, then retry. Entitlements are managed in Billing; there is no per-count RESOURCE_EXHAUSTED limit on this gate.

Lifecycle

A Connected Business moves through ConnectedBusinessStatus:

Status Meaning
CONNECTED_BUSINESS_STATUS_PROVISIONING Being created; tenant and credentials are being provisioned.
CONNECTED_BUSINESS_STATUS_ACTIVE Fully operational.
CONNECTED_BUSINESS_STATUS_SUSPENDED Access revoked and billing paused; data preserved.
CONNECTED_BUSINESS_STATUS_DESTROYING Being permanently deleted.
flowchart LR
  P["PROVISIONING"] --> A["ACTIVE"]
  A -- "suspend" --> S["SUSPENDED"]
  S -- "reactivate" --> A
  A -- "delete" --> D["DESTROYING"]
  S -- "delete" --> D

Create a connected business

The request body has exactly two fields: name and adminEmail. There is no country or taxId at creation — fill those in afterward via UpdateConnectedBusiness and the connected business's own settings. The response returns the new tenant record plus its API credentials.

curl -X POST https://gateway.invora.app/api/v2/identity/connected-businesses \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Riyadh Branch",
    "adminEmail": "admin@acme-riyadh.com"
  }'
Response
{
  "connectedBusiness": {
    "tenantId": "317842111002338820",
    "name": "Acme Riyadh Branch",
    "parentTenantId": "317842069254438913",
    "status": "CONNECTED_BUSINESS_STATUS_PROVISIONING",
    "audit": {
      "createdAt": "2026-06-29T10:00:00Z",
      "createdBy": "317842069254438913",
      "updatedAt": "2026-06-29T10:00:00Z",
      "updatedBy": "317842069254438913"
    }
  },
  "clientId": "317842111002338821@acme-riyadh",
  "clientSecret": "Qd2v...redacted...7m"
}

Warning

As with the parent profile, clientSecret is shown once. Store it securely or rotate later with RotateCredentials.

A ConnectedBusiness record is { tenantId, name, parentTenantId, status, audit } — note it carries no country or taxId, and status is a ConnectedBusinessStatus enum value (never the bare string "active").

Get a connected business

curl -X GET https://gateway.invora.app/api/v2/identity/connected-businesses/317842111002338820 \
  -H "Authorization: Bearer $TOKEN"
Response
{
  "connectedBusiness": {
    "tenantId": "317842111002338820",
    "name": "Acme Riyadh Branch",
    "parentTenantId": "317842069254438913",
    "status": "CONNECTED_BUSINESS_STATUS_ACTIVE",
    "audit": {
      "createdAt": "2026-06-29T10:00:00Z",
      "createdBy": "317842069254438913",
      "updatedAt": "2026-06-29T10:05:00Z",
      "updatedBy": "317842069254438913"
    }
  }
}

List connected businesses

A standard list endpoint: a structured filter (with composable part and textSearch) plus pagination. Filter parts support status (in-values), country, and createdAt. The response returns items, totalCount, and an optional nextPageCursor. See List APIs for the full filtering, sorting, and pagination conventions.

curl -X POST https://gateway.invora.app/api/v2/identity/connected-businesses/list \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "textSearch": "riyadh",
      "part": { "status": { "inValues": ["CONNECTED_BUSINESS_STATUS_ACTIVE"] } }
    },
    "pagination": { "limit": 20 }
  }'
Response
{
  "items": [
    {
      "tenantId": "317842111002338820",
      "name": "Acme Riyadh Branch",
      "parentTenantId": "317842069254438913",
      "status": "CONNECTED_BUSINESS_STATUS_ACTIVE",
      "audit": {
        "createdAt": "2026-06-29T10:00:00Z",
        "createdBy": "317842069254438913",
        "updatedAt": "2026-06-29T10:05:00Z",
        "updatedBy": "317842069254438913"
      }
    }
  ],
  "totalCount": 1,
  "nextPageCursor": null
}

Update a connected business

PUT to the tenant path. Updates use optimistic concurrency: pass the concurrencyStamp from the latest read plus a mask listing the fields you are changing. The only mutable field is name.

curl -X PUT https://gateway.invora.app/api/v2/identity/connected-businesses/317842111002338820 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "concurrencyStamp": "a1b2c3d4",
    "mask": "name",
    "name": "Acme Riyadh (Olaya) Branch"
  }'
Response
{
  "connectedBusiness": {
    "tenantId": "317842111002338820",
    "name": "Acme Riyadh (Olaya) Branch",
    "parentTenantId": "317842069254438913",
    "status": "CONNECTED_BUSINESS_STATUS_ACTIVE",
    "audit": {
      "createdAt": "2026-06-29T10:00:00Z",
      "createdBy": "317842069254438913",
      "updatedAt": "2026-06-29T11:00:00Z",
      "updatedBy": "317842069254438913"
    }
  }
}

Suspend a connected business

Revokes the business's API access and pauses its billing. Data is preserved. Provide a reason for the audit trail.

curl -X POST https://gateway.invora.app/api/v2/identity/connected-businesses/317842111002338820/suspend \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Non-payment" }'
Response
{
  "connectedBusiness": {
    "tenantId": "317842111002338820",
    "name": "Acme Riyadh (Olaya) Branch",
    "parentTenantId": "317842069254438913",
    "status": "CONNECTED_BUSINESS_STATUS_SUSPENDED",
    "audit": {
      "createdAt": "2026-06-29T10:00:00Z",
      "createdBy": "317842069254438913",
      "updatedAt": "2026-06-29T12:00:00Z",
      "updatedBy": "317842069254438913"
    }
  }
}

Reactivate a connected business

Restores API access and resumes billing for a suspended business.

curl -X POST https://gateway.invora.app/api/v2/identity/connected-businesses/317842111002338820/reactivate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Response
{
  "connectedBusiness": {
    "tenantId": "317842111002338820",
    "name": "Acme Riyadh (Olaya) Branch",
    "parentTenantId": "317842069254438913",
    "status": "CONNECTED_BUSINESS_STATUS_ACTIVE",
    "audit": {
      "createdAt": "2026-06-29T10:00:00Z",
      "createdBy": "317842069254438913",
      "updatedAt": "2026-06-29T12:30:00Z",
      "updatedBy": "317842069254438913"
    }
  }
}

Rotate credentials

Issues a new OIDC clientId/clientSecret for the connected business and revokes the old credentials immediately. Any integration using the previous secret stops working at once — roll it out before rotating.

curl -X POST https://gateway.invora.app/api/v2/identity/connected-businesses/317842111002338820/rotate-credentials \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
Response
{
  "clientId": "317842111002338899@acme-riyadh",
  "clientSecret": "Zx9a...redacted...4t"
}

Delete a connected business

Permanently deletes the connected business and all its data. Irreversible — suspend instead if you may need to restore later. The status transitions to CONNECTED_BUSINESS_STATUS_DESTROYING while the cascade runs.

curl -X DELETE https://gateway.invora.app/api/v2/identity/connected-businesses/317842111002338820 \
  -H "Authorization: Bearer $TOKEN"
Response
{}

List branches across connected businesses

Lists branches across all Connected Businesses under your organization, so a parent platform can see the full branch structure of its network in one call. Optionally scope to a single connected business with tenantId. Takes a top-level textSearch and pagination; returns branch records with items, totalCount, and an optional nextPageCursor.

curl -X POST https://gateway.invora.app/api/v2/identity/connected-businesses/branches/list \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "textSearch": "riyadh",
    "pagination": { "limit": 50 }
  }'
Response
{
  "items": [
    {
      "tenantId": "317842111002338820",
      "tenantName": "Acme Riyadh (Olaya) Branch",
      "branchId": "br_main",
      "branchName": "Head Office",
      "isPrimary": true,
      "country": "SA"
    }
  ],
  "totalCount": 1,
  "nextPageCursor": null
}

Becoming a platform

To create Connected Businesses, a standard Business must first be upgraded to Platform type. Upgrading is an enrollment application that Invora staff review.

There is no public RPC in this API to submit or self-approve that application. Submission happens through the dashboard's platform-enrollment flow, and the review is handled by Invora staff.

An application carries the org name, why platform capabilities are needed, expected number of connected businesses, expected monthly volume, technical contact email, and terms-of-service acceptance.

Review statuses (PlatformApplicationStatus):

Status Meaning
PENDING Awaiting review.
APPROVED Org upgraded to Platform; Connected Business endpoints become available.
REJECTED Org stays a Business; rejection reason provided.
INFO_REQUESTED Invora needs more information before deciding.

Reviewer decisions (PlatformApplicationDecision): APPROVE, REJECT, REQUEST_INFO.

RPC reference

PublicRegistrationService

All RPCs require an authenticated user token; no tenant membership or admin role is needed (a freshly registered user can call them before their organization exists).

RPC Method & path Scope
CompleteBusinessProfile POST /api/identity/v2/registration/complete-profile Invora.Identity.v2.CompleteBusinessProfile
ListAvailablePlans GET /api/identity/v2/registration/available-plans Invora.Identity.v2.ListAvailablePlans
SelectPlan POST /api/identity/v2/registration/select-plan Invora.Identity.v2.SelectPlan
UpdateBusinessDetails PUT /api/identity/v2/registration/business-details Invora.Identity.v2.UpdateBusinessDetails
GetOnboardingStatus GET /api/identity/v2/registration/onboarding-status Invora.Identity.v2.GetOnboardingStatus

ConnectedBusinessService

Platform-tier RPCs. Creating a Connected Business additionally requires the connected business plan capability (see the capability gate).

RPC Method & path Scope
CreateConnectedBusiness POST /api/v2/identity/connected-businesses Invora.Identity.v2.ConnectedBusiness.Create
GetConnectedBusiness GET /api/v2/identity/connected-businesses/{tenantId} Invora.Identity.v2.ConnectedBusiness.Get
ListConnectedBusinesses POST /api/v2/identity/connected-businesses/list Invora.Identity.v2.ConnectedBusiness.List
UpdateConnectedBusiness PUT /api/v2/identity/connected-businesses/{tenantId} Invora.Identity.v2.ConnectedBusiness.Update
SuspendConnectedBusiness POST /api/v2/identity/connected-businesses/{tenantId}/suspend Invora.Identity.v2.ConnectedBusiness.Suspend
ReactivateConnectedBusiness POST /api/v2/identity/connected-businesses/{tenantId}/reactivate Invora.Identity.v2.ConnectedBusiness.Reactivate
RotateCredentials POST /api/v2/identity/connected-businesses/{tenantId}/rotate-credentials Invora.Identity.v2.ConnectedBusiness.RotateCredentials
DeleteConnectedBusiness DELETE /api/v2/identity/connected-businesses/{tenantId} Invora.Identity.v2.ConnectedBusiness.Delete
ListConnectedBusinessBranches POST /api/v2/identity/connected-businesses/branches/list Invora.Identity.v2.ConnectedBusiness.ListBranches

Error handling

gRPC code HTTP When
ALREADY_EXISTS 409 Business profile already completed.
PERMISSION_DENIED 403 Missing scope, or — for CreateConnectedBusiness — your plan lacks the connected business capability.
NOT_FOUND 404 The tenant ID does not exist or is not under your organization.
ABORTED 409 Stale concurrencyStamp on update — re-read and retry.
FAILED_PRECONDITION 400 An operation invalid for the current status (e.g. reactivating a non-suspended business).
INVALID_ARGUMENT 400 Missing or invalid fields (empty business name, invalid country code, unknown plan code).

See Error handling for the full status-code mapping and the structured error-detail payloads.