Skip to content

Machine-to-Machine Authentication

Applies to: All subscriptions

Purpose

Explain how an ERP proves its identity to Complifly, how its authority is bounded, and how to move from static keys to identity-provider tokens without interrupting a live integration.

Audience

Integration developers, identity administrators, security reviewers.

Prerequisites

  • Authentication and Security
  • For OAuth: an identity tenant and the ability to register an application
  • For static keys: administrator access to issue them

Reference

Two mechanisms, accepted side by side

The ingest and integration endpoints accept either mechanism. Both are supported simultaneously so that a live integration can migrate without an outage.

OAuth token Static key
Header Authorization: Bearer <token> X-API-Key: cfly_..., or the same value as a bearer token
Lifetime Short-lived, refreshed by the client Long-lived until rotated
Revocation At the identity provider In the Complifly admin console
Suits New integrations; enterprises with an identity platform Simple integrations; migration from an earlier deployment
Security posture Preferred Acceptable, with disciplined rotation

Both produce the same authorisation context downstream, so no endpoint behaves differently depending on which was used.

OAuth, in practice

The application is registered in your own Microsoft Entra directory, not in Complifly's. Complifly never holds your client secret; it stays in your directory and you rotate it there on your own schedule. What Complifly stores is two public identifiers — your directory (tenant) ID and your application (client) ID — neither of which can authenticate anything on its own.

Your ERP obtains a token from your directory using the client-credentials flow. Complifly validates it in this order:

# Validation Consequence if wrong
1 The directory and application are a known pair, and the connection is active Rejected as an unknown client, before anything else happens
2 Signature, against your directory's published keys Token rejected
3 Issuer is exactly your directory. Both token-version issuer forms are accepted Rejected
4 Audience. Both the application identifier URI and the bare identifier may be needed, depending on token version Rejected
5 The required application role Rejected as forbidden, not unauthorised — the token is valid, it simply lacks the granted role

Step 1 comes first deliberately. A token from a directory Complifly does not serve is refused without any lookup against that directory at all.

The role check at step 5 is the one integration developers most often stumble on. A token that authenticates but is refused usually means the application role was never granted through admin consent — not that the credential is wrong.

Your directory and your application ID are both required

An application ID does not identify you by itself. The same application can be installed in many directories and keeps the same ID in each, so Complifly recognises a connection only as the pair: this application, in this directory.

Two consequences follow, and both are normal:

  • A connection cannot be made live before its directory is known. There is nothing to pair the application with.
  • The same application ID may legitimately appear under two different customers. That is not a conflict.

Onboarding a connection, step by step

Onboarding spans two organisations, so it is not instant. Complifly tracks which stage a connection has reached, and both sides can see who is holding it up.

Stage Waiting on What has to happen
PENDING_CONSENT You Your Global Administrator opens the consent link
AWAITING_CLIENT You You send Complifly your application (client) ID
ACTIVE Tokens are accepted
SUSPENDED Complifly Deliberately switched off

Work through the steps in order. Steps 1 and 6 are Complifly's; steps 2 to 5 happen in your own Entra directory, and Complifly cannot see or perform them.

1. Complifly creates the connection

Not customer configurable

This step requires Platform Super Admin in Complifly. Your Complifly account manager performs it; a customer administrator cannot. Ask for it, giving your company and the list of GSTINs the ERP will submit for.

Complifly records the connection against your company and its permitted GSTINs, and sends you an admin-consent link. The connection is now at PENDING_CONSENT.

The link is valid for 15 minutes and can be used once. Have your Global Administrator ready before asking for it. A lapsed link is expected, not a fault — ask for a fresh one.

Intended for your identity administrator

This step needs Global Administrator or Privileged Role Administrator in your Entra directory. A Complifly administrator cannot do it for you.

  1. Open the consent link in a browser signed in as that administrator.
  2. Read the permission being requested and accept it.
  3. You are returned to a Complifly confirmation page.

This provisions the Complifly API's service principal in your directory — which is what makes the API selectable in your own app registrations at step 4. Your Directory (tenant) ID is captured automatically, and the connection moves to AWAITING_CLIENT.

If the browser never returns to Complifly, your network blocked the redirect. Your administrator's consent still counts and nothing needs repeating — send Complifly your Directory (tenant) ID (Entra admin centre → Overview) and it is recorded directly.

3. Register your ERP's application

In the Entra admin centre, as an administrator of your directory:

  1. Go to Identity → Applications → App registrations.
  2. Select New registration.
  3. Name it for the ERP and the environment — SAP S/4 Production → Complifly, not App1.
  4. Leave Supported account types at single tenant. A redirect URI is not needed: the client-credentials flow does not use one.
  5. Select Register.
  6. On the Overview page, copy the Application (client) ID and the Directory (tenant) ID. You need the first at step 5.

Give each ERP its own registration. Sharing one across ERPs makes it impossible to revoke a single integration without breaking the others, and makes the two indistinguishable to Complifly — which matters when they share a GSTIN, because a shared registration is one connection and receives one write-back stream.

4. Grant your application the Invoice.Post role

Still in your new app registration:

  1. Open API permissions → Add a permission.
  2. Choose APIs my organisation uses and search for the Complifly API. It appears here only because step 2 provisioned it; if it is missing, step 2 did not complete.
  3. Select Application permissions — not delegated. There is no signed-in user in this flow.
  4. Tick the Invoice.Post role.
  5. Select Add permissions, then Grant admin consent for <your directory>, and confirm the status column reads Granted.

The role is named Invoice.Post unless your deployment overrides it through the AZURE_REQUIRED_ROLE setting. If a token is refused as forbidden while everything else checks out, confirm the expected role name with Complifly.

Step 5 of token validation checks this role. Consent at step 2 does not grant it — that consent installed the API in your directory; this step grants your application permission to call it. Missing this is the single most common OAuth stumble, and it presents as a valid token refused with 403.

5. Send Complifly the application (client) ID

Send the Application (client) ID from step 3. Complifly pairs it with the directory captured at step 2, and the connection goes ACTIVE.

Never send your client secret. Complifly does not need it, does not store it, and cannot use it. It stays in your directory, and you rotate it there on your own schedule.

6. Prove it end to end

Have the ERP acquire a token from your directory using the client-credentials flow, and call a low-risk endpoint:

GET /api/v1/integration/health
Authorization: Bearer <token>

Any response other than 401 or 403 means authentication and authorisation both work — even 404 NO_SUBSCRIPTION.

What your administrator needs, in one list

Item Where it comes from
Global Administrator (or Privileged Role Administrator) Your directory
The consent link Complifly — valid 15 minutes, single use
Application role Invoice.Post on the Complifly API Selected when you add the API permission at step 4
Your application's Application (client) ID App registration overview — send this to Complifly
Your Directory (tenant) ID Directory overview — needed only if the redirect is blocked

Your client secret is not on that list and must never be sent to Complifly.

Static keys, in practice

A key is issued in the admin console and shown once. Store it in a secret manager at that moment; it cannot be retrieved later.

Property Detail
Scope Company-wide. A key may submit for any GSTIN registered under its company. Per-GSTIN scoping is an OAuth capability
Revocation Immediate, from the console. Permanent
Rotation Issue the new key, migrate the caller, then revoke the old one
OAuth-only mode A key's record can be marked so that the key is refused and the client must use OAuth. Reversible, and the safer first step

Keys issued before this change may still carry a GSTIN restriction. Some older keys have a single GSTIN recorded against them, and that restriction is still enforced for those keys. Newly issued keys are company-wide. If an existing integration is refused for a registration you expected it to serve, this is the likely cause — check the key's recorded scope rather than assuming the credential is broken.

Two ways to close a key

Require OAuth refuses that client's static key while leaving everything else untouched. A caller already on OAuth notices nothing; one still using the key is told clearly to switch. It is reversible, which makes it the right first move when you are not certain who still holds the key.

Revoke is permanent. Reach for it once you are sure.

Neither undoes work already done — documents posted with a key remain valid.

GSTIN scope is the real authorisation boundary

A credential's authority is bounded by the registrations it may act for. A document whose supplier registration is outside that boundary is refused, regardless of anything else the credential may do.

This is the property to explain to a security reviewer: a compromised ERP credential can submit documents only for the registrations that ERP was ever entitled to.

Where the boundary comes from differs by mechanism, and the difference matters:

Mechanism Boundary
OAuth connection The explicit GSTIN list attached to that connection. One connection can cover a portfolio of registrations, or exactly one
Static key with a recorded GSTIN That one registration
Static key without one Every GSTIN registered under the key's company
Situation Outcome
Document's supplier GSTIN is in scope Accepted
Out of scope Refused as forbidden
Credential scoped to several registrations All are accepted

An OAuth connection is therefore the tighter instrument: its list is set deliberately at onboarding, whereas a company-wide key follows whatever registrations are added to the company later.

A forbidden response on ingest is almost always a scope mismatch, not a broken credential — a distinction worth building into the ERP's own error handling.

Migrating from keys to OAuth, step by step

The dual-path design exists for exactly this, and it can be done without an outage. Both mechanisms stay open throughout, so there is no cut-over moment at which traffic can fall on the floor.

  1. Complete the consent handshake above, so the connection reaches ACTIVE. Check: ask Complifly for the connection's stage. AWAITING_CLIENT means step 5 above was never done.
  2. Have the ERP acquire a token and call a low-risk endpoint. Check: accepted. A 403 here is the Invoice.Post role, not the credential.
  3. Switch the ERP's production calls to the token. Check: documents are still being accepted, at the usual rate.
  4. Watch for a full business cycle, including month end. Check: no code path fell back to the key. Do not skip this — month-end processing frequently uses paths that never run during a normal week.
  5. Mark the key OAuth-only, so it is refused. Check: a deliberate call with the old key is refused. This is reversible, which is what makes it the right move before revocation.
  6. Once a further cycle passes cleanly, revoke the key. Check: the credential list shows no key that is both superseded and still accepted.

A superseded key still works until you close it. Moving an ERP to OAuth does not disable its old key. Both paths stay open, and only one of them is being watched. The admin console lists which keys would still be accepted today — treat a superseded-but-accepted key as an open door, not as history.

Validation

Check Method Pass condition
Connection reached ACTIVE Ask Complifly for the connection's stage ACTIVE, not AWAITING_CLIENT
Credential authenticates Call a low-risk endpoint Accepted
Token comes from your own directory Decode the token and read tid Your directory ID, not Complifly's
An unknown application is refused Present a token from an app registration Complifly was never told about Refused as an unknown client
Scope enforced Submit a document for an out-of-scope registration Refused as forbidden
Revocation is immediate Revoke, then call again Refused
Role requirement enforced Present a token without the required role Refused as forbidden, not unauthorised
Audience validated Present a token for a different audience Rejected
Expiry enforced Present an expired token Rejected with an expiry indication
Key not recoverable Attempt to view an issued key in the console Not retrievable
One credential per ERP Review issued credentials Each ERP has its own
Rotation rehearsed Perform a full rotation in a non-production environment Completes without interrupting the integration
Secrets stored properly Review where the ERP holds the credential In a secret manager, not in code or a configuration file in source control
Client secret never shared Search your correspondence with Complifly The secret appears nowhere. Only the application and directory IDs were sent
Superseded keys closed Review the credential list after migrating to OAuth No key is both superseded and still accepted

Troubleshooting

Symptom Cause Action
HTTP 403 "not authorised for Complifly" on the first ever call The connection was never made live — Complifly has your directory but not your application ID, or neither Send Complifly your application (client) ID. Check the connection's stage before debugging anything else
HTTP 403 immediately after moving the ERP to a new app registration The new application ID was never sent to Complifly. The pair no longer matches Send the new application ID. A registration change is a connection change
HTTP 401 with a token that looks correct Wrong audience, wrong issuer, expired, or clock drift Decode the token and compare audience and issuer against the configuration. Check host time synchronisation
HTTP 403 with a valid token The required application role was not granted Grant it through admin consent. This is the most common OAuth stumble
HTTP 403 on ingest, credential otherwise fine The document's supplier GSTIN is outside the credential's scope Widen the scope deliberately, or send from the correct credential
Static key rejected although not revoked The key has been marked OAuth-only Complete the OAuth migration for that client
Key lost Keys are shown once and are not retrievable Issue a new key, migrate, revoke the old one
Token validation fails after working for months Identity provider signing keys rotated and cannot be fetched Confirm outbound access to the provider's key endpoint
All integrations fail at once after a change One credential shared across several ERPs Issue separate credentials per ERP
Cannot revoke one integration without breaking others Same cause Same fix
Intermittent authentication failures Clock drift on the ERP or application host Correct time synchronisation. Token validation is time-sensitive
Integration works in testing, fails at month end A code path exercised only at month end uses a different credential or endpoint Test through a full cycle before decommissioning the old credential
The consent link gives an AADSTS error before any Complifly page appears The link expired, was already used, or the redirect address does not match what is registered Ask for a fresh link. If it fails again, report the full AADSTS code — it is raised inside your directory and Complifly cannot see it
Consent completed but the browser never returned to a Complifly page Your network blocked the redirect Nothing needs repeating. Send your Directory (tenant) ID so it can be recorded directly
Consent succeeded, tokens still refused Consent provisions the API in your directory; it does not by itself grant your ERP's application the role Add the API permission to your own application, select Invoice.Post, and grant admin consent for that application too
The old static key still works after the OAuth cut-over Moving to OAuth does not disable the key Mark the key OAuth-only, then revoke it once a full cycle has passed
Two ERPs on one GSTIN, and only one receives write-back events They share an application registration, so they are one connection Give each ERP its own registration and its own connection