
You're building a financial reconciliation flow when you hit a wall. The accounting system your company uses doesn't have a built-in Power Automate connector, but it does have a REST API. Your flow needs to automatically pull transaction data every night, validate it against your CRM records, and push discrepancies to a tracking system. Standard connectors won't cut it—you need direct API control.
This is where Power Automate's HTTP actions and custom connectors become essential tools. While pre-built connectors handle common scenarios, real-world automation often requires direct API integration. HTTP actions give you the flexibility to connect to any REST API, while custom connectors let you package that functionality for reuse and sharing across your organization.
What you'll learn:
You should be comfortable building flows with standard connectors, understand basic HTTP concepts (GET, POST, status codes), and have experience with JSON data manipulation in Power Automate. Familiarity with REST APIs is helpful but not required.
Power Automate's HTTP connector provides three main actions: HTTP, HTTP with Azure AD, and Webhook. The standard HTTP action handles most scenarios, while HTTP with Azure AD simplifies authentication for Azure-protected resources.
Let's start with a practical example. You need to integrate with a vendor's invoice API that requires API key authentication and returns paginated results. Here's how to structure this systematically:
{
"method": "GET",
"uri": "https://api.vendorname.com/v2/invoices",
"headers": {
"Authorization": "Bearer @{parameters('API_Key')}",
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "PowerAutomate-Integration/1.0"
},
"queries": {
"limit": "50",
"offset": "@{mul(variables('pageNumber'), 50)}",
"status": "pending",
"created_after": "@{formatDateTime(addDays(utcNow(), -7), 'yyyy-MM-dd')}"
}
}
The key elements here are authentication headers, proper content-type specification, and dynamic query parameters. The User-Agent header identifies your integration to the API provider—many require this for support purposes.
For POST requests with data, structure your request body carefully:
{
"method": "POST",
"uri": "https://api.vendorname.com/v2/invoices/@{variables('invoiceId')}/approve",
"headers": {
"Authorization": "Bearer @{parameters('API_Key')}",
"Content-Type": "application/json"
},
"body": {
"approved_by": "@{triggerBody()?['approver_email']}",
"approved_at": "@{utcNow()}",
"notes": "@{triggerBody()?['approval_notes']}",
"internal_reference": "@{guid()}"
}
}
Notice how we're using Power Automate expressions to populate dynamic values. The guid() function generates unique tracking IDs, while triggerBody() pulls data from the flow trigger.
Different APIs require different authentication approaches. API keys are straightforward, but OAuth 2.0 flows require more sophisticated handling.
For OAuth 2.0 authentication, you'll often need a two-step process. First, obtain an access token:
{
"method": "POST",
"uri": "https://oauth.provider.com/token",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"body": "grant_type=client_credentials&client_id=@{parameters('ClientId')}&client_secret=@{parameters('ClientSecret')}&scope=invoice:read invoice:write"
}
Then use the returned token in subsequent requests:
{
"method": "GET",
"uri": "https://api.provider.com/data",
"headers": {
"Authorization": "Bearer @{body('HTTP_Get_Token')?['access_token']}"
}
}
Security tip: Always store sensitive credentials like API keys and client secrets as flow parameters or environment variables, never hard-code them in the flow definition.
Production integrations must handle failures gracefully. Power Automate provides built-in retry policies, but you need to configure them appropriately for your scenario.
Configure retry settings on your HTTP action:
{
"retryPolicy": {
"type": "exponential",
"count": 4,
"interval": "PT20S",
"maximumInterval": "PT1H",
"minimumInterval": "PT5S"
}
}
This implements exponential backoff: 20 seconds, then 40, then 80, then 160 seconds between retries. The maximum interval prevents excessive delays.
For more sophisticated error handling, wrap your HTTP actions in try-catch blocks using parallel branches:
{
"actions": {
"Try_HTTP_Request": {
"type": "Scope",
"actions": {
"HTTP_API_Call": {
// your HTTP action here
}
}
},
"Catch_Errors": {
"type": "Scope",
"actions": {
"Handle_API_Error": {
"type": "Switch",
"expression": "@outputs('HTTP_API_Call')['statusCode']",
"cases": {
"429": {
// Rate limit - wait and retry
},
"401": {
// Authentication failed - refresh token
},
"500": {
// Server error - log and alert
}
}
}
},
"runAfter": {
"Try_HTTP_Request": ["Failed", "TimedOut"]
}
}
}
}
When you're repeatedly using the same API across multiple flows, or when you need to share API integration with team members, custom connectors provide a cleaner solution. Custom connectors wrap HTTP operations into reusable, configurable actions.
Let's build a custom connector for a hypothetical expense management API. Start in the Power Automate portal by navigating to Data > Custom connectors > New custom connector > Create from blank.
Define the connector's basic information:
In the Security section, configure authentication. For our example, we'll use API Key authentication:
Now define your connector's actions. Each action corresponds to an API endpoint. For a "Get Expenses" action:
General information:
Request configuration:
Parameters:
For the response, provide a sample JSON response so Power Automate can understand the data structure:
{
"expenses": [
{
"id": "exp_123456",
"employee_id": "emp_789",
"amount": 125.50,
"currency": "USD",
"category": "Travel",
"description": "Client meeting lunch",
"receipt_url": "https://receipts.expensetracker.com/receipt_123456.pdf",
"submitted_date": "2024-01-15T10:30:00Z",
"status": "submitted"
}
],
"total_count": 1,
"page": 1,
"per_page": 50
}
Create a "Submit Expense" action for POST operations:
Request configuration:
Request body schema:
{
"type": "object",
"properties": {
"employee_id": {
"type": "string",
"description": "Employee ID"
},
"amount": {
"type": "number",
"description": "Expense amount"
},
"currency": {
"type": "string",
"description": "Currency code (USD, EUR, etc.)"
},
"category": {
"type": "string",
"description": "Expense category"
},
"description": {
"type": "string",
"description": "Expense description"
},
"receipt_data": {
"type": "string",
"description": "Base64 encoded receipt image"
}
},
"required": ["employee_id", "amount", "currency", "category"]
}
Custom connectors support sophisticated features for production use. Policy templates let you transform requests and responses without modifying the underlying API.
Add a policy template to automatically handle pagination:
<policies>
<inbound>
<set-query-parameter name="page" value="@{policy.request.query['page'] ?? '1'}" />
<set-query-parameter name="per_page" value="@{policy.request.query['per_page'] ?? '50'}" />
</inbound>
<outbound>
<set-body>
@{
var response = policy.response.body;
if (response != null && response.data != null) {
return Json.Stringify(new {
items = response.data,
nextLink = response.pagination?.next_page != null ?
"https://api.expensetracker.com/v3/expenses?page=" + response.pagination.next_page : null
});
}
return policy.response.body;
}
</set-body>
</outbound>
</policies>
This policy transforms the API's pagination format into Power Automate's expected format, making it easier to use in flows.
For OAuth 2.0 APIs, configure the security settings with proper scopes and redirect URLs:
OAuth 2.0 Configuration:
The connector handles the OAuth flow automatically, presenting users with an authentication dialog when they first use the connector.
Before deploying custom connectors, thoroughly test each operation. Use the built-in test functionality in the custom connector editor, but also create dedicated test flows.
Create a comprehensive test flow that exercises all connector operations:
Document expected behavior for each test case:
{
"test_cases": [
{
"name": "Get Expenses - Valid Request",
"action": "GetExpenses",
"parameters": {
"employee_id": "emp_123",
"start_date": "2024-01-01"
},
"expected_result": "Success with expense array"
},
{
"name": "Get Expenses - No Results",
"action": "GetExpenses",
"parameters": {
"employee_id": "emp_999"
},
"expected_result": "Success with empty array"
},
{
"name": "Submit Expense - Invalid Amount",
"action": "SubmitExpense",
"parameters": {
"amount": -50.00
},
"expected_result": "400 Bad Request error"
}
]
}
Let's build a practical custom connector that sends rich notifications to Slack channels. This exercise demonstrates real-world connector development from start to finish.
Step 1: Slack App Setup
First, create a Slack app in your workspace:
Step 2: Create the Custom Connector
In Power Automate, create a new custom connector:
General Tab:
Security Tab:
Definition Tab - Send Message Action:
Create an action called "Send Rich Message":
Parameters:
Request Body Schema:
{
"type": "object",
"properties": {
"channel": {"type": "string"},
"text": {"type": "string"},
"username": {"type": "string"},
"icon_emoji": {"type": "string"},
"attachments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"color": {"type": "string"},
"title": {"type": "string"},
"text": {"type": "string"},
"fields": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"value": {"type": "string"},
"short": {"type": "boolean"}
}
}
}
}
}
}
},
"required": ["channel", "text"]
}
Step 3: Add File Upload Action
Create a second action for uploading files:
Parameters:
Step 4: Test the Connector
Test both actions with realistic data:
Rich Message Test:
{
"channel": "#general",
"text": "Expense Report Alert",
"username": "ExpenseBot",
"icon_emoji": ":money_with_wings:",
"attachments": [
{
"color": "warning",
"title": "New Expense Report Pending",
"text": "Employee John Doe submitted an expense report requiring approval",
"fields": [
{
"title": "Amount",
"value": "$1,250.00",
"short": true
},
{
"title": "Category",
"value": "Travel",
"short": true
},
{
"title": "Submitted",
"value": "2024-01-15 10:30 AM",
"short": false
}
]
}
]
}
Step 5: Create a Production Flow
Build a flow that uses your custom connector in a realistic scenario:
This demonstrates how custom connectors integrate seamlessly with other Power Automate actions.
Authentication Issues
The most common problem is authentication configuration. Always test authentication separately before building complex flows. For API keys, verify the header name and format requirements—some APIs expect "Authorization: Bearer token", others use custom headers like "X-API-Key: token".
JSON Schema Mismatches
When APIs return unexpected data structures, flows fail with schema validation errors. Build robust flows that handle varying response formats:
{
"condition": {
"expression": "@and(not(empty(body('HTTP_Action'))), contains(body('HTTP_Action'), 'data'))",
"then": {
"processData": "@body('HTTP_Action')['data']"
},
"else": {
"processData": "@body('HTTP_Action')"
}
}
}
Rate Limiting
APIs often implement rate limits that cause 429 errors. Implement exponential backoff and respect rate limit headers:
{
"retryAfter": "@if(contains(outputs('HTTP_Action')['headers'], 'Retry-After'), outputs('HTTP_Action')['headers']['Retry-After'], '60')"
}
Custom Connector Deployment
Custom connectors created in one environment don't automatically appear in others. Export your connector definition and import it into each target environment. For organization-wide deployment, work with your Power Platform administrator to deploy as a certified connector.
Testing in Different Environments
APIs behave differently in development versus production environments. Test your connectors against production-like data volumes and real API rate limits, not just development sandbox APIs.
When building flows that make multiple HTTP requests, consider the performance implications. Sequential API calls can create long-running flows that may timeout. Instead, use parallel branches for independent operations:
{
"parallel_branches": {
"get_customer_data": {
"actions": ["HTTP_Get_Customer"]
},
"get_order_history": {
"actions": ["HTTP_Get_Orders"]
},
"get_payment_methods": {
"actions": ["HTTP_Get_Payments"]
}
}
}
For bulk operations, implement batching when the API supports it. Instead of making 100 individual requests, make 10 requests with 10 items each:
{
"batch_size": 10,
"batched_items": "@chunk(variables('all_items'), 10)",
"process_batch": {
"type": "Foreach",
"foreach": "@variables('batched_items')",
"actions": {
"HTTP_Batch_Process": {
"method": "POST",
"body": "@items('process_batch')"
}
}
}
}
Monitor your flows' performance using the Power Automate analytics dashboard. Look for patterns in failure rates and execution times that might indicate API reliability issues or rate limiting problems.
HTTP actions and custom connectors unlock Power Automate's full integration potential. You've learned to build robust API integrations with proper authentication, error handling, and retry logic. Custom connectors let you package these integrations for reuse and sharing across your organization.
The key principles to remember:
Next steps for advancement:
Custom connectors become especially powerful when combined with Power Platform's other components. Consider how your connectors might integrate with Power Apps for user interfaces, or Power BI for data visualization of API-sourced data.
Learning Path: Flow Automation Basics