Hero image for Skip the Clicks: Why API Integration Beats UI Automation Every Time

Skip the Clicks: Why API Integration Beats UI Automation Every Time

RPA UiPath API REST Integration

Here’s a dirty secret of RPA: the best automation doesn’t click anything.

While beginners spend hours perfecting selectors and handling popup windows, experts are calling APIs and finishing in milliseconds. The difference isn’t marginal —it’s orders of magnitude.

In this article, we’ll explore when and how to bypass the UI entirely.


The Case Against UI Automation

Let’s be honest about what UI automation actually is: a workaround.

We click buttons because the system doesn’t give us a better option. But clicking is:

UI AutomationAPI Integration
30+ seconds per form0.1 seconds per request
Breaks on any UI changeImmune to visual changes
Requires screen resolutionRuns headless, anywhere
One action at a timeParallel requests possible
Visible on screenInvisible background process
Complex selectorsClean JSON payloads

If a system offers an API, use it.


When to Use Each Approach

Use API When:

  • System has documented REST/SOAP endpoints
  • You’re extracting or inserting structured data
  • High volume processing (hundreds/thousands of transactions)
  • Process runs unattended in background
  • You need maximum speed and reliability

Use UI When:

  • No API exists (legacy mainframes, terminal apps)
  • API lacks specific functionality you need
  • One-time or low-volume process
  • Vendor won’t provide API access
  • Human-in-the-loop approval steps

Hybrid Approach

Many enterprise solutions combine both:

graph LR
    %% 定義節點
    subgraph Systems [Target Systems]
        direction TB
        SF[("Salesforce<br/>(Modern App)<br/><br/>")]:::apiSystem
        ERP[("Legacy ERP<br/>(Old System)<br/><br/>")]:::uiSystem
    end

    Bot((RPA Bot)):::bot

    %% 定義連接與速度
    SF ==>|API Integration<br/>⚡ 100 records/min<br/><br/>| Bot
    ERP -.->|UI Interaction<br/>🐢 2 records/min<br/><br/>| Bot

    %% 樣式設定
    classDef apiSystem fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
    classDef uiSystem fill:#ffebee,stroke:#c62828,stroke-width:2px;
    classDef bot fill:#e3f2fd,stroke:#1565c0,stroke-width:4px;
    
    %% 連線樣式
    linkStyle 0 stroke:#2e7d32,stroke-width:3px;
    linkStyle 1 stroke:#c62828,stroke-width:3px,stroke-dasharray: 5 5;

Data Flow in Hybrid Architecture:

sequenceDiagram
    participant Mail as 📩 Email
    participant Bot as 🤖 RPA Bot
    participant AI as 🧠 Document AI
    participant SAP as ⚡ SAP (API)
    participant Legacy as 🐢 Legacy Portal (UI)

    Note over Bot: Process Start

    Bot->>Mail: Get Transaction Item (PDF)
    Bot->>AI: Send PDF File
    AI-->>Bot: Return JSON {vendor, amount...}
    
    Bot->>SAP: Check Vendor Existence (API Lookup)
    SAP-->>Bot: Return Boolean (True/False)

    alt Vendor Exists (Happy Path)
        Bot->>SAP: POST Invoice Data (BAPI)
        SAP-->>Bot: Return SAP Document #
    else Vendor Missing (Fallback Path)
        Note right of Bot: ⚠️ Switch to UI Automation
        Bot->>Legacy: Open Browser & Login
        Bot->>Legacy: Scrape/Input Data Manually
        Legacy-->>Bot: Return Document #
    end

    Note over Bot: Update Status & End

[!TIP] Design Principle: Always start with API paths. Only fall back to UI when API is unavailable or missing specific functionality. This maximizes speed while maintaining coverage.


REST API Fundamentals

Most modern systems use REST APIs. Here’s what you need to know:

HTTP Methods

MethodPurposeExample
GETRetrieve dataGet invoice details
POSTCreate new recordCreate new customer
PUTReplace entire recordUpdate customer profile
PATCHUpdate partial recordChange customer email only
DELETERemove recordDelete draft invoice

HTTP Status Codes

CodeMeaningAction
200 OKSuccessContinue processing
201 CreatedResource createdCapture new ID from response
400 Bad RequestInvalid inputCheck your payload format
401 UnauthorizedAuth failedRefresh token or check credentials
403 ForbiddenNo permissionContact admin for access
404 Not FoundResource doesn’t existHandle gracefully
429 Too Many RequestsRate limitedImplement backoff and retry
500 Server ErrorTheir problemRetry later

Request Components

POST https://api.example.com/v1/invoices
Headers:
  Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
  Content-Type: application/json
  X-Request-ID: uuid-12345

Body:
{
  "vendor_id": "V001",
  "amount": 1234.56,
  "currency": "USD",
  "due_date": "2024-02-15"
}

Finding APIs: Swagger and OpenAPI

Before you can integrate, you need to discover what the API offers.

What is Swagger/OpenAPI?

Simple Analogy: Swagger is like an “API menu” or “API instruction manual”. Just like a restaurant menu tells you what dishes are available, how much they cost, and what ingredients they contain, Swagger tells you what API functions you can call, what parameters are needed, and what data will be returned.

OpenAPI (formerly Swagger) is the industry standard for API documentation. Think of it as:

Real World AnalogySwagger/OpenAPI Equivalent
Restaurant MenuAPI Endpoint List
Dish NameFunction Name (GET /users, POST /orders)
Ingredient ListRequired Parameters
PriceResponse Format
How to OrderAuthentication Method

Most modern APIs provide an OpenAPI spec that tells you:

  • Available endpoints
  • Required parameters
  • Request/response formats
  • Authentication methods

Why does this matter for RPA developers?

Without Swagger, you would need to:

  1. Ask the development team for documentation (often outdated)
  2. Guess the correct parameter names and formats
  3. Trial-and-error until something works

With Swagger, you can:

  1. Browse all available functions in a web interface
  2. Test the API directly in your browser before writing any code
  3. Copy exact request formats into your UiPath workflow

Finding API Documentation

SystemWhere to Find API Docs
SalesforceSettings → API → API Documentation or developer.salesforce.com
SAPSAP API Business Hub
ServiceNowSystem Web Services → REST API Explorer
Microsoft 365Microsoft Graph Explorer
Custom SystemsAsk IT: “Do you have a Swagger/OpenAPI spec?” or try /swagger, /api-docs

Pro Tip: When asking IT for API access, use the magic phrase: “Do you have a Swagger doc?” This shows you understand API integration and makes the conversation much more productive.

API Discovery: When IT Says “We Don’t Have an API”

[!IMPORTANT] Hidden APIs Are Everywhere
Many web applications are Single Page Apps (SPAs) that communicate with REST APIs behind the scenes. Even if there’s no public documentation, the API exists — you just need to find it.

How to Discover Hidden APIs with Chrome DevTools:

1. Open the web application in Chrome
2. Press F12 → Network tab
3. Filter by "Fetch/XHR" (API calls only)
4. Perform the action manually (e.g., click "Submit")
5. Watch the Network tab for API calls

What to Look For:

ColumnWhat It Tells You
NameThe endpoint URL (e.g., /api/v1/invoices)
MethodGET, POST, PUT, etc.
Status200 = success, 401 = need auth
HeadersAuthentication method used
PayloadRequest body (JSON format)
ResponseWhat data comes back

Example Discovery:

You click "Submit Invoice" in browser

DevTools shows: POST https://internal.company.com/api/invoices

Payload: {"vendor_id": "V001", "amount": 1234.56}

Response: {"invoice_id": "INV-789", "status": "created"}

✓ You just found an undocumented API!

[!CAUTION] Ethics and Permission: Always get IT approval before using discovered APIs. Undocumented APIs may change without notice and lack support agreements.

Swagger UI: Try Before You Code

Many APIs expose an interactive Swagger UI at /swagger or /api-docs:

https://api.yourcompany.com/swagger
https://api.yourcompany.com/api-docs

This lets you:

  1. Browse all available endpoints
  2. Test requests directly in browser (no code needed!)
  3. See exact request/response formats
  4. Generate code samples

UiPath HTTP Request Activity

UiPath’s HTTP Request activity is your gateway to APIs.

Basic GET Request

Activity: HTTP Request
├── End Point: https://api.example.com/v1/customers/C001
├── Method: GET
├── Headers:
│   └── Authorization: Bearer {{accessToken}}
└── Output: responseBody (String)

POST with JSON Body

Activity: HTTP Request
├── End Point: https://api.example.com/v1/invoices
├── Method: POST
├── Headers:
│   ├── Authorization: Bearer {{accessToken}}
│   └── Content-Type: application/json
├── Body: {"vendor_id":"V001","amount":1234.56}
├── Body Format: application/json
└── Output: responseBody, statusCode

Handling the Response

' Check if successful
If statusCode = 200 Or statusCode = 201 Then
    ' Parse JSON response
    jsonResponse = JObject.Parse(responseBody)
    newInvoiceId = jsonResponse("id").ToString()
    Log.Info($"Created invoice: {newInvoiceId}")
Else
    ' Handle error
    Log.Error($"API Error {statusCode}: {responseBody}")
    Throw New BusinessRuleException($"API returned {statusCode}")
End If

JSON Parsing in UiPath

API responses are typically JSON. Here’s how to work with them:

Simple Value Extraction

{
  "id": "INV-001",
  "amount": 1234.56,
  "vendor": {
    "name": "Acme Corp",
    "country": "USA"
  }
}
' Parse the JSON
json = JObject.Parse(responseBody)

' Get simple values
invoiceId = json("id").ToString()           ' "INV-001"
amount = json("amount").Value(Of Decimal)() ' 1234.56

' Get nested values
vendorName = json("vendor")("name").ToString()  ' "Acme Corp"

Array Processing

{
  "invoices": [
    {"id": "INV-001", "amount": 100},
    {"id": "INV-002", "amount": 200},
    {"id": "INV-003", "amount": 300}
  ]
}
json = JObject.Parse(responseBody)
invoices = json("invoices").ToObject(Of JArray)()

' Loop through array
For Each invoice As JObject In invoices
    id = invoice("id").ToString()
    amount = invoice("amount").Value(Of Decimal)()
    Log.Info($"Invoice {id}: ${amount}")
Next

' LINQ on JSON array
totalAmount = invoices.Sum(Function(i) i("amount").Value(Of Decimal)())
highValue = invoices.Where(Function(i) i("amount").Value(Of Decimal)() > 150).ToList()

Dynamic JSON Construction

' Build request body dynamically
requestBody = New JObject()
requestBody("vendor_id") = vendorId
requestBody("amount") = amount
requestBody("line_items") = New JArray()

For Each item In lineItems
    lineItem = New JObject()
    lineItem("description") = item("Description")
    lineItem("quantity") = item("Qty")
    lineItem("unit_price") = item("Price")
    requestBody("line_items").Add(lineItem)
Next

jsonString = requestBody.ToString()

The Professional Way: Deserialize to Class

Parsing JSON with JObject is fine for simple cases. But for production code, deserializing to strongly-typed classes is superior.

Why Deserialize to Class?

ApproachJObject.Parse()Deserialize to Class
Type Safety? Runtime errors? Compile-time checking
IntelliSense? No autocomplete? Full autocomplete
Refactoring? String-based, brittle? IDE-supported renames
Null Handling? Manual checks everywhere? Class properties handle
Performance? Slower dictionary lookups? Faster direct access

Step 1: Define Your Classes

' In Invoke Code or custom library
Public Class InvoiceResponse
    Public Property id As String
    Public Property amount As Decimal
    Public Property vendor As Vendor
    Public Property line_items As List(Of LineItem)
End Class

Public Class Vendor
    Public Property name As String
    Public Property country As String
End Class

Public Class LineItem
    Public Property description As String
    Public Property quantity As Integer
    Public Property unit_price As Decimal
End Class

Step 2: Deserialize

' Deserialize JSON to strongly-typed object
invoice = JsonConvert.DeserializeObject(Of InvoiceResponse)(responseBody)

' Now you have IntelliSense and type safety!
vendorName = invoice.vendor.name          ' No more json("vendor")("name").ToString()
totalAmount = invoice.amount              ' Already a Decimal, no casting
firstItem = invoice.line_items.First()    ' Full LINQ support

Step 3: Handle Nullable Fields

Public Class ApiResponse
    Public Property data As List(Of Record)
    Public Property errors As List(Of String)  ' May be null
    Public Property next_page As String         ' May be null
End Class

' Safe access
If apiResponse.errors IsNot Nothing AndAlso apiResponse.errors.Any() Then
    HandleErrors(apiResponse.errors)
End If

hasNextPage = Not String.IsNullOrEmpty(apiResponse.next_page)

Pro Tip: Use online tools like json2csharp.com or jsonformatter.org to auto-generate classes from sample JSON.

[!NOTE] Required NuGet Packages for Serialization

  • UiPath.WebAPI.Activities — HTTP Request activity
  • Newtonsoft.Json — JSON parsing (JObject, JsonConvert)

How to Generate Classes Faster:

  1. Copy sample JSON response
  2. Use QuickType.io → Select VB.NET or C#
  3. Paste into your UiPath project as an Invoke Code or Library

This avoids typos and handles edge cases (nullable fields, arrays) automatically.

JSON to Class Mapping Visualization:

graph LR
    %% 定義樣式
    classDef json fill:#fff3e0,stroke:#ff9800,stroke-width:2px;
    classDef vb fill:#e3f2fd,stroke:#2196f3,stroke-width:2px;

    %% 左側:JSON 結構
    subgraph JSON_Data [JSON Structure]
        direction TB
        J_ID("id: 'INV-001'"):::json
        J_Amt("amount: 1234.56"):::json
        J_Ven("vendor: { ... }"):::json
    end

    %% 右側:VB.NET 結構
    subgraph VB_Class [VB.NET Class Properties]
        direction TB
        V_ID("Property id As String"):::vb
        V_Amt("Property amount As Decimal"):::vb
        V_Ven("Property vendor As Vendor"):::vb
    end

    %% 連接線 (Mapping)
    J_ID <-->|Map| V_ID
    J_Amt <-->|Map| V_Amt
    J_Ven <-->|Map| V_Ven

Authentication Patterns

Pattern 1: API Key

Simplest approach —a static key in the header:

Headers:
  X-API-Key: your-secret-key-here

Store in Orchestrator Asset (Text type, encrypted).

[!CAUTION] Asset Type Matters for Security

Secret TypeCorrect Asset TypeWhy
API KeyText (encrypted)Single value, no username
Username + PasswordCredentialStores pair, password never logged
Client Secret (OAuth)Text (encrypted) or CredentialTreat as password

Never store passwords as regular Text Assets —use Credential type so the password is encrypted and never appears in logs or exports.

[!WARNING] Secret Lifecycle Management

  • Rotate secrets regularly: Client Secrets should expire every 90-180 days
  • Never hardcode in comments: ' TODO: secret is abc123 is a security breach waiting to happen
  • Use different secrets per environment: Dev/UAT/Prod should have separate API keys

Pattern 2: Basic Auth

Username and password, Base64 encoded:

credentials = Convert.ToBase64String(
    Encoding.UTF8.GetBytes($"{username}:{password}")
)

' Header
Authorization: Basic {credentials}

Pattern 3: OAuth 2.0 (Bearer Token)

Most enterprise APIs use OAuth 2.0. Understanding the flow is critical.

stateDiagram-v2
    direction LR

    state "No Token / Expired" as NoToken
    state "Valid Token" as Valid
    
    [*] --> NoToken
    
    NoToken --> Valid : POST /oauth/token (Authenticate)
    
    state Valid {
        [*] --> Idle
        Idle --> CallingAPI : RPA needs Data
        CallingAPI --> Idle : Data Received
    }

    Valid --> NoToken : Expires after 3600s
    
    note right of Valid
        Reuse this token
        for multiple calls
        to save API limits<BR /><BR />
    end note

OAuth Grant Types

Grant TypeUse CaseRPA Suitability
Client CredentialsServer-to-server (no user)✓ Best for unattended bots
Authorization CodeUser login required~ Requires browser interaction
Password (ROPC)Legacy, deprecated✗ Avoid, security risk
Refresh TokenExtend session✓ Use when provided

Token Management Workflow

' Token caching pattern
Private Shared accessToken As String
Private Shared tokenExpiry As DateTime

Public Function GetValidToken() As String
    ' Check if token is still valid (with 60s buffer)
    If accessToken Is Nothing OrElse tokenExpiry < DateTime.Now.AddSeconds(60) Then
        ' Get new token
        tokenResponse = CallTokenEndpoint(clientId, clientSecret)
        accessToken = tokenResponse("access_token").ToString()
        tokenExpiry = DateTime.Now.AddSeconds(
            tokenResponse("expires_in").Value(Of Integer)()
        )
        Log.Info($"New token acquired, expires at {tokenExpiry}")
    End If
    
    Return accessToken
End Function

Pattern 4: Certificate-Based Auth

For maximum security (banking, government):

Activity: HTTP Request
├── Client Certificate Path: C:\Certs\client.pfx
├── Client Certificate Password: {{certPassword}}
└── ...

Rate Limiting and Retry

APIs limit request frequency. Handle it gracefully:

Detecting Rate Limits

If statusCode = 429 Then
    ' Check Retry-After header
    retryAfter = responseHeaders("Retry-After")
    If retryAfter IsNot Nothing Then
        waitSeconds = Integer.Parse(retryAfter)
    Else
        waitSeconds = 60  ' Default wait
    End If
    
    Log.Warn($"Rate limited. Waiting {waitSeconds} seconds.")
    Delay(waitSeconds * 1000)
    ' Retry
End If

Exponential Backoff Pattern

maxRetries = 5
For attempt = 1 To maxRetries
    Try
        response = CallAPI(endpoint)
        Exit For  ' Success, exit loop
    Catch ex As Exception
        If attempt = maxRetries Then Throw
        
        waitTime = Math.Pow(2, attempt) * 1000  ' 2s, 4s, 8s, 16s, 32s
        Log.Warn($"Attempt {attempt} failed. Waiting {waitTime/1000}s")
        Delay(waitTime)
    End Try
Next

Circuit Breaker Pattern: Fail Fast

For high-volume API calls, simple retry can make things worse. If the API is down, hammering it with retries may:

  • Get your account rate-limited or blocked
  • Overload an already struggling server
  • Waste processing time on guaranteed failures

The Circuit Breaker Concept:

stateDiagram-v2
    direction LR

    %% 定義狀態與詳細說明
    state "CLOSED (Normal)" as Closed {
        [*] --> Processing
        Processing : API calls go through
        Processing : Track failure count
    }

    state "OPEN (Failing)" as Open {
        [*] --> Blocked
        Blocked : All calls blocked
        Blocked : Fail immediately
    }

    state "HALF-OPEN (Testing)" as HalfOpen {
        [*] --> Probing
        Probing : Test one call
        Probing : Check result
    }

    %% 定義轉換條件
    Closed --> Open : Failure Threshold (5 errors)
    Open --> HalfOpen : Timeout (60 seconds)
    
    HalfOpen --> Closed : Success (Recovery)
    HalfOpen --> Open : Failure (Still down)

    %% 註解說明
    note right of Open
        Prevents system overload
        during outages
    end note

Simple Implementation:

' Shared state (in Init or Invoke Code)
Private Shared consecutiveFailures As Integer = 0
Private Shared circuitOpenUntil As DateTime = DateTime.MinValue

Public Function CallAPIWithCircuitBreaker(endpoint As String) As String
    ' Check if circuit is open
    If DateTime.Now < circuitOpenUntil Then
        Throw New ApplicationException("Circuit breaker OPEN - skipping API call")
    End If
    
    Try
        response = CallAPI(endpoint)
        consecutiveFailures = 0  ' Reset on success
        Return response
        
    Catch ex As Exception
        consecutiveFailures += 1
        If consecutiveFailures >= 5 Then
            ' Open the circuit for 60 seconds
            circuitOpenUntil = DateTime.Now.AddSeconds(60)
            Log.Error("Circuit breaker OPENED - too many failures")
        End If
        Throw
    End Try
End Function

[!TIP] When to Use Circuit Breaker: High-volume batch processing (100+ API calls), external third-party APIs with unreliable uptime, or any scenario where continued retrying causes more harm than failing fast.


Common API Integrations in RPA

Salesforce

' Query records with SOQL
endpoint = "https://yourinstance.salesforce.com/services/data/v55.0/query"
query = "SELECT Id, Name, Amount FROM Opportunity WHERE StageName = 'Closed Won'"
url = $"{endpoint}?q={HttpUtility.UrlEncode(query)}"

response = GET(url, bearerToken)
opportunities = JObject.Parse(response)("records")

SAP (OData)

' Get sales orders via OData
endpoint = "https://sap.company.com/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder"
params = "$filter=SalesOrderType eq 'OR'&$top=100"

response = GET($"{endpoint}?{params}", basicAuthHeader)
orders = JObject.Parse(response)("d")("results")

Microsoft Graph (Office 365)

' Get emails from shared mailbox
endpoint = "https://graph.microsoft.com/v1.0/users/shared@company.com/messages"
params = "$filter=isRead eq false&$top=50"

response = GET($"{endpoint}?{params}", bearerToken)
emails = JObject.Parse(response)("value")

ServiceNow

' Create incident ticket
endpoint = "https://company.service-now.com/api/now/table/incident"
body = New JObject()
body("short_description") = "RPA: Invoice processing failure"
body("description") = errorDetails
body("urgency") = "2"
body("category") = "software"

response = POST(endpoint, body.ToString(), basicAuth)
ticketNumber = JObject.Parse(response)("result")("number").ToString()

Testing APIs with Postman

Before coding in UiPath, test in Postman:

Workflow

  1. Get documentation: Find the API reference
  2. Set up collection: Organize related endpoints
  3. Configure auth: Add credentials to collection
  4. Test endpoints: Verify responses
  5. Export for reference: Save successful requests

Postman to UiPath Translation

PostmanUiPath HTTP Request
URLEnd Point
Method dropdownMethod property
Headers tabHeaders dictionary
Body > raw > JSONBody + BodyFormat
Response bodyOutput variable

Error Handling for APIs

The Defensive Pattern

Try
    ' Make API call
    response = HTTP_Request(endpoint, method, body, headers)
    
    ' Validate response
    If statusCode < 200 Or statusCode >= 300 Then
        Throw New ApplicationException($"API Error: {statusCode}")
    End If
    
    ' Parse response
    json = JObject.Parse(response)
    
    ' Validate expected fields
    If json("id") Is Nothing Then
        Throw New BusinessRuleException("Response missing required 'id' field")
    End If
    
    ' Extract data
    resultId = json("id").ToString()
    
Catch jsonEx As JsonReaderException
    ' Malformed response
    Log.Error($"Invalid JSON response: {response}")
    Throw New SystemException("API returned invalid JSON", jsonEx)
    
Catch httpEx As HttpRequestException
    ' Network/connection issue
    Log.Error($"Network error calling API: {httpEx.Message}")
    Throw New SystemException("Cannot connect to API", httpEx)
    
Catch ex As Exception
    ' Unexpected error
    Log.Error($"Unexpected API error: {ex.Message}")
    Throw
End Try

Performance Comparison

Real numbers from a production invoice processing system:

MetricUI AutomationAPI IntegrationImprovement
Time per invoice45 seconds0.3 seconds150x faster
Daily capacity640 invoices96,000 invoices150x more
Failure rate3.2%0.1%32x more stable
Maintenance hours/month8 hours0.5 hours16x less work

The math is clear: APIs win on every metric.


Key Takeaways

  1. API first: Always check if an API exists before automating UI.
  2. Find Swagger/OpenAPI docs: Most modern systems have them.
  3. Deserialize to classes: Don’t parse JSON with string keys in production.
  4. Master OAuth 2.0: Client Credentials flow is your bread and butter.
  5. Speed difference is massive: 100x-1000x faster is normal.
  6. Postman is essential: Test before you code.
  7. Handle failures gracefully: Rate limits, timeouts, and errors happen.

The best RPA developers aren’t the fastest button clickers —they’re the ones who avoid clicking altogether.