CryptoNetHub

Building an ERP Integration Layer for AI Agents: SAP, Oracle, NetSuite Architecture Patterns

Nobody writes about this because it's not glamorous. It's also where most enterprise AI projects actually live or die. Every enterprise AI agent demo I've seen looks the same: clean API calls, mock data,. . .

· 4 min read

Building an ERP Integration Layer for AI Agents: SAP, Oracle, NetSuite Architecture Patterns

Nobody writes about this because it's not glamorous. It's also where most enterprise AI projects actually live or die. Every enterprise AI agent demo I've seen looks the same: clean API calls, mock data, instant responses. Then someone tries to connect it to actual SAP S/4HANA running in a client's data centre with a service account that was provisioned in 2019, and the demo timeline becomes a different project entirely. ERP integration is the unglamorous 70% of enterprise AI agent work that determines whether your agent actually functions in production. This is the architecture guide I wish existed when we built our first finance agent integration. The Authentication Layer: Three Patterns, Three ERPsSAP S/4HANA - OAuth 2. 0 with SAML bridgingSAP's modern API layer (via SAP Business Technology Platform) uses OAuth 2. 0, but most enterprise SAP environments still run a hybrid setup with legacy RFC connections behind the scenes. importrequestsfromdatetimeimportdatetime,timedeltaclassSAPAuthManager:def__init__(self,client_id,client_secret,token_url):self. client_id=client_idself. client_secret=client_secretself. token_url=token_urlself. _token=Noneself. _expiry=Nonedefget_token(self)->str:ifself. _tokenanddatetime. now()dict:return{"Authorization":f"Bearer{self. get_token()}","Content-Type":"application/json"}Enter fullscreen modeExit fullscreen modeOracle Fusion - Service accounts with JWTOracle Fusion Cloud uses OAuth 2. 0 with JWT bearer tokens, typically via a dedicated service account with scoped REST API roles. importjwtimporttimeimportrequestsclassOracleFusionAuth:def__init__(self,client_id,private_key,token_url,scope):self. client_id=client_idself. private_key=private_keyself. token_url=token_urlself. scope=scopedefbuild_jwt_assertion(self)->str:now=int(time. time())payload={"iss":self. client_id,"sub":self. client_id,"aud":self. token_url,"exp":now+300,"iat":now}returnjwt. encode(payload,self. private_key,algorithm="RS256")defget_token(self)->str:assertion=self. build_jwt_assertion()response=requests. post(self. token_url,data={"grant_type":"urn:ietf:params:oauth:grant-type:jwt-bearer","assertion":assertion,"scope":self. scope})response. raise_for_status()returnresponse. json()["access_token"]Enter fullscreen modeExit fullscreen modeNetSuite - Token-based authentication (TBA)NetSuite uses OAuth 1. 0a-style token-based auth, which is older but well-documented. importhashlibimporthmacimportbase64importtimeimportsecretsclassNetSuiteTBA:def__init__(self,account_id,consumer_key,consumer_secret,token_id,token_secret):self. account_id=account_idself. consumer_key=consumer_keyself. consumer_secret=consumer_secretself. token_id=token_idself. token_secret=token_secretdefbuild_auth_header(self,method:str,url:str)->str:nonce=secrets. token_hex(16)timestamp=str(int(time. time()))base_string="&". join([method,url,f"oauth_consumer_key={self. consumer_key}",f"oauth_nonce={nonce}",f"oauth_signature_method=HMAC-SHA256",f"oauth_timestamp={timestamp}",f"oauth_token={self. token_id}",f"oauth_version=1. 0"])signing_key=f"{self. consumer_secret}&{self. token_secret}"signature=base64. b64encode(hmac. new(signing_key. encode(),base_string. encode(),hashlib. sha256). digest()). decode()return(f'OAuth realm="{self. account_id}",'f'oauth_consumer_key="{self. consumer_key}",'f'oauth_token="{self. token_id}",'f'oauth_signature_method="HMAC-SHA256",'f'oauth_timestamp="{timestamp}",'f'oauth_nonce="{nonce}",'f'oauth_version="1. 0",'f'oauth_signature="{signature}"')Enter fullscreen modeExit fullscreen modeRBAC matters across all three: provision the service account with the minimum scope needed for your agent's actions, never broad admin access. We've seen integrations provisioned with full ERP admin rights because it was "easier to set up", this is a security finding waiting to happen in any audit. Data Sync: Event-Driven vs PollingThe architecture decision that affects everything downstream is whether your agent reacts to ERP events or polls for changes. # POLLING PATTERN - simpler, higher latency, works everywhereclassERPPollingSync:def__init__(self,erp_client,poll_interval_seconds=60):self. erp_client=erp_clientself. poll_interval=poll_interval_secondsself. last_sync_timestamp=Noneasyncdefpoll_for_changes(self):whileTrue:changes=awaitself. erp_client. get_changed_records(since=self. last_sync_timestamp)forrecordinchanges:awaitself. process_change(record)self. last_sync_timestamp=datetime. utcnow()awaitasyncio. sleep(self. poll_interval)# EVENT-DRIVEN PATTERN - lower latency, requires ERP webhook supportclassERPEventSync:def__init__(self,webhook_secret):self. webhook_secret=webhook_secretasyncdefhandle_webhook(self,request_body:bytes,signature:str):ifnotself. verify_signature(request_body,signature):raiseValueError("Invalid webhook signature")event=json. loads(request_body)# Idempotency check - ERP webhooks can fire duplicatesifawaitself. already_processed(event["event_id"]):return{"status":"duplicate_ignored"}awaitself. process_event(event)awaitself. mark_processed(event["event_id"])Enter fullscreen modeExit fullscreen modeSAP S/4HANA supports event-driven architecture via SAP Event Mesh. Oracle Fusion supports business event subscriptions. NetSuite's webhook support is more limited, polling is often the pragmatic choice there. Idempotency: Non-Negotiable for Financial TransactionsEvery write operation your agent performs against an ERP needs an idempotency key. Network failures, retries, and webhook duplicates will otherwise create duplicate financial records. classIdempotentERPWriter:def__init__(self,erp_client,idempotency_store):self. erp_client=erp_clientself. store=idempotency_storeasyncdefcreate_invoice(self,invoice_data:dict,idempotency_key:str)->dict:existing=awaitself. store. get(idempotency_key)ifexisting:returnexisting# Already processed - return cached resulttry:result=awaitself. erp_client. create_invoice(invoice_data)awaitself. store. set(idempotency_key,result,ttl_hours=24)returnresultexceptExceptionase:awaitself. store. set(idempotency_key,{"status":"failed","error":str(e)},ttl_hours=1)raiseEnter fullscreen modeExit fullscreen modeAudit Logging: Built In, Not Bolted OnEvery agent action against an ERP needs an immutable audit record, what changed, who (or which agent) initiated it, and why. asyncdefexecute_erp_action(action:dict,agent_context:dict):audit_record={"agent_id":agent_context["agent_id"],"action_type":action["type"],"erp_system":action["target_system"],"record_affected":action["record_id"],"reasoning":agent_context["decision_rationale"],"timestamp":datetime. utcnow(). isoformat(),"idempotency_key":action["idempotency_key"]}awaitaudit_store. append(audit_record)# append-only, immutableresult=awaiterp_client. execute(action)audit_record["result"]=resultaudit_record["completed_at"]=datetime. utcnow(). isoformat()awaitaudit_store. append(audit_record)returnresultEnter fullscreen modeExit fullscreen modeERP Downtime: Plan for ItERP systems have maintenance windows and occasional outages. Your agent needs graceful degradation, not crashes. classResilientERPClient:def__init__(self,erp_client,circuit_breaker):self. erp_client=erp_clientself. circuit_breaker=circuit_breakerasyncdefcall_with_resilience(self,operation,*args):ifself. circuit_breaker. is_open():return{"status":"deferred","reason":"ERP circuit breaker open"}try:result=awaitoperation(*args)self. circuit_breaker. record_success()returnresultexceptERPTimeoutError:self. circuit_breaker. record_failure()return{"status":"queued_for_retry"}Enter fullscreen modeExit fullscreen modeWhat This Means for Your Project TimelineERP integration is consistently underestimated in enterprise AI projects. The authentication setup alone, coordinating with the client's ERP admin team, provisioning service accounts, testing RBAC scopes, regularly takes longer than the AI agent's core logic development. The fullERP integration architecture for AI agent finance workflowscovers the complete reference architecture for all three ERP systems, including the specific gotchas we've hit with each. ERP integration is one piece of enterprise AI agent deployment. Thefull enterprise AI agents in finance guidecovers the rest of the stack, orchestration, compliance, monitoring, for teams building the complete system. Published by Dextra Labs | AI Consulting & Enterprise Agent Development

Source: dev.to

More Layer 2 news