---
name: Certyo + Power Automate Integration
version: 1.0.0
description: Generate Microsoft Power Automate → Certyo integration code with custom connectors, flow templates, and working examples
api_base: https://www.certyos.com
auth: X-API-Key header
last_updated: 2026-04-14
---

# Certyo + Power Automate Integration Skill

This skill generates production-ready Microsoft Power Automate integrations that ingest records into Certyo's blockchain-anchored authenticity platform. It produces custom connector definitions, flow templates for common triggers, and deployment scripts.

**Licensing**: Power Automate Premium tier ($15/user/month) is required for custom connectors and HTTP actions.

## Certyo API Reference

### Endpoints

| Method | Path | Description | Response |
|--------|------|-------------|----------|
| `POST` | `/api/v1/records` | Ingest a single record | `202 Accepted` |
| `POST` | `/api/v1/records/bulk` | Ingest up to 1000 records | `202 Accepted` |
| `POST` | `/api/v1/verify/record` | Verify blockchain anchoring | `200 OK` |
| `GET` | `/api/v1/records` | Query records | `200 OK` |

### Authentication

All requests require the `X-API-Key` header with a valid Certyo API key.

### Record Payload

```json
{
  "tenantId": "string (required)",
  "database": "string (required)",
  "collection": "string (required)",
  "recordId": "string (required)",
  "recordPayload": { "...any JSON (required)" },
  "clientId": "string (optional)",
  "recordVersion": "string (optional, default '1')",
  "operationType": "upsert|insert|update|delete (optional)",
  "sourceTimestamp": "ISO 8601 (optional)",
  "idempotencyKey": "string (optional)"
}
```

### Response (202 Accepted)

```json
{
  "recordId": "string",
  "recordHash": "string (SHA-256)",
  "tenantId": "string",
  "acceptedAt": "ISO 8601",
  "idempotencyReplayed": false
}
```

### Pipeline

Record ingestion flows through: Kafka accumulation (1000 records or 60s flush) then Merkle tree computation then IPFS pin then Polygon anchor (~60-90s end to end).

## Integration Pattern

```
Trigger (D365 / SharePoint / SQL Server / Dataverse)
        |
  Compose Action (build Certyo payload)
        |
  Custom Connector: Certyo (POST /api/v1/records)
        |
  Condition (check 202 response)
        |
  Update Source / Send Notification
```

The custom connector wraps all Certyo API endpoints so flows use a clean, reusable action instead of raw HTTP requests. This enables governance, connection sharing across the organization, and centralized API key management.

## Authentication

### Custom Connector with API Key Auth

The Certyo custom connector uses API Key authentication. The `X-API-Key` header is configured once at the connection level and automatically injected into every request.

Power Automate encrypts the API key at rest and transmits it over TLS. Users create a connection by entering their Certyo API key, and the connector manages the header injection.

## OpenAPI 3.0 Specification for Custom Connector

Save this as the custom connector definition. Import it in the Power Automate Maker Portal under Custom Connectors.

**`certyo-connector-openapi.json`**:

```json
{
  "openapi": "3.0.1",
  "info": {
    "title": "Certyo Blockchain Authenticity",
    "description": "Ingest records into Certyo for blockchain-anchored authenticity verification. Records are accumulated, Merkle-hashed, pinned to IPFS, and anchored on Polygon.",
    "version": "1.0.0",
    "contact": {
      "name": "Certyo Support",
      "url": "https://www.certyos.com"
    }
  },
  "servers": [
    {
      "url": "https://www.certyos.com"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/api/v1/records": {
      "post": {
        "operationId": "IngestRecord",
        "summary": "Ingest a single record for blockchain anchoring",
        "description": "Accepts a record payload and queues it for Merkle tree computation, IPFS pinning, and Polygon anchoring. Returns 202 with the record hash.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Record accepted for processing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecordResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "QueryRecords",
        "summary": "Query ingested records",
        "parameters": [
          {
            "name": "tenantId",
            "in": "query",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "recordId",
            "in": "query",
            "schema": { "type": "string" }
          },
          {
            "name": "database",
            "in": "query",
            "schema": { "type": "string" }
          },
          {
            "name": "collection",
            "in": "query",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Records matching the query"
          }
        }
      }
    },
    "/api/v1/records/bulk": {
      "post": {
        "operationId": "IngestRecordsBulk",
        "summary": "Ingest up to 1000 records in a single request",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "maxItems": 1000,
                "items": {
                  "$ref": "#/components/schemas/RecordRequest"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Records accepted for processing"
          }
        }
      }
    },
    "/api/v1/verify/record": {
      "post": {
        "operationId": "VerifyRecord",
        "summary": "Verify a record against the blockchain anchor",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification result"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "RecordRequest": {
        "type": "object",
        "required": ["tenantId", "database", "collection", "recordId", "recordPayload"],
        "properties": {
          "tenantId": { "type": "string", "description": "Tenant identifier" },
          "database": { "type": "string", "description": "Source database name" },
          "collection": { "type": "string", "description": "Source collection or table" },
          "recordId": { "type": "string", "description": "Unique record identifier" },
          "recordPayload": { "type": "object", "description": "Full record data to anchor" },
          "clientId": { "type": "string", "description": "Optional client identifier" },
          "recordVersion": { "type": "string", "default": "1" },
          "operationType": {
            "type": "string",
            "enum": ["upsert", "insert", "update", "delete"]
          },
          "sourceTimestamp": { "type": "string", "format": "date-time" },
          "idempotencyKey": { "type": "string", "description": "Prevents duplicate processing" }
        }
      },
      "RecordResponse": {
        "type": "object",
        "properties": {
          "recordId": { "type": "string" },
          "recordHash": { "type": "string", "description": "SHA-256 hash" },
          "tenantId": { "type": "string" },
          "acceptedAt": { "type": "string", "format": "date-time" },
          "idempotencyReplayed": { "type": "boolean" }
        }
      },
      "VerifyRequest": {
        "type": "object",
        "required": ["tenantId", "recordId", "recordHash"],
        "properties": {
          "tenantId": { "type": "string" },
          "recordId": { "type": "string" },
          "recordHash": { "type": "string" }
        }
      }
    }
  }
}
```

## Flow Templates

### Flow 1: Dynamics 365 Record Change to Certyo

This flow triggers whenever a record is created or updated in Dynamics 365 (Dataverse) and ingests it into Certyo.

```json
{
  "definition": {
    "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
    "contentVersion": "1.0.0.0",
    "triggers": {
      "When_a_row_is_added_or_modified": {
        "type": "OpenApiConnectionTrigger",
        "inputs": {
          "host": {
            "connectionName": "shared_commondataserviceforapps",
            "operationId": "SubscribeWebhookTrigger",
            "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
          },
          "parameters": {
            "subscriptionRequest/message": 4,
            "subscriptionRequest/entityname": "@parameters('D365_EntityName')",
            "subscriptionRequest/scope": 4,
            "subscriptionRequest/filterexpression": "@parameters('D365_FilterExpression')"
          }
        },
        "description": "Triggers on Dataverse row create or update"
      }
    },
    "actions": {
      "Compose_Certyo_Payload": {
        "type": "Compose",
        "inputs": {
          "tenantId": "@parameters('Certyo_TenantId')",
          "database": "dynamics365",
          "collection": "@parameters('D365_EntityName')",
          "recordId": "@triggerOutputs()?['body/accountid']",
          "recordPayload": "@triggerOutputs()?['body']",
          "operationType": "upsert",
          "sourceTimestamp": "@triggerOutputs()?['body/modifiedon']",
          "idempotencyKey": "@concat('d365:', triggerOutputs()?['body/accountid'], ':', triggerOutputs()?['body/versionnumber'])"
        },
        "runAfter": {},
        "description": "Build Certyo record from D365 trigger data"
      },
      "Ingest_Record_to_Certyo": {
        "type": "OpenApiConnection",
        "inputs": {
          "host": {
            "connectionName": "shared_certyo",
            "operationId": "IngestRecord",
            "apiId": "/providers/Microsoft.PowerApps/apis/shared_certyo"
          },
          "parameters": {
            "body": "@outputs('Compose_Certyo_Payload')"
          }
        },
        "runAfter": {
          "Compose_Certyo_Payload": ["Succeeded"]
        },
        "retryPolicy": {
          "type": "exponential",
          "count": 4,
          "interval": "PT10S",
          "minimumInterval": "PT5S",
          "maximumInterval": "PT1H"
        },
        "description": "Send record to Certyo via custom connector"
      },
      "Condition_Check_Response": {
        "type": "If",
        "expression": {
          "and": [
            {
              "equals": ["@outputs('Ingest_Record_to_Certyo')['statusCode']", 202]
            }
          ]
        },
        "actions": {
          "Update_D365_Record_Status": {
            "type": "OpenApiConnection",
            "inputs": {
              "host": {
                "connectionName": "shared_commondataserviceforapps",
                "operationId": "UpdateRecord",
                "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
              },
              "parameters": {
                "entityName": "@parameters('D365_EntityName')",
                "recordId": "@triggerOutputs()?['body/accountid']",
                "item/certyo_status": "Ingested",
                "item/certyo_recordhash": "@body('Ingest_Record_to_Certyo')?['recordHash']",
                "item/certyo_acceptedat": "@body('Ingest_Record_to_Certyo')?['acceptedAt']"
              }
            },
            "description": "Write Certyo hash back to D365 record"
          }
        },
        "else": {
          "actions": {
            "Send_Failure_Notification": {
              "type": "OpenApiConnection",
              "inputs": {
                "host": {
                  "connectionName": "shared_office365",
                  "operationId": "SendEmailV2",
                  "apiId": "/providers/Microsoft.PowerApps/apis/shared_office365"
                },
                "parameters": {
                  "emailMessage/To": "@parameters('Admin_Email')",
                  "emailMessage/Subject": "Certyo Ingestion Failed",
                  "emailMessage/Body": "@concat('Record ', triggerOutputs()?['body/accountid'], ' failed to ingest. Status: ', outputs('Ingest_Record_to_Certyo')['statusCode'])"
                }
              }
            }
          }
        },
        "runAfter": {
          "Ingest_Record_to_Certyo": ["Succeeded", "Failed"]
        }
      }
    },
    "parameters": {
      "Certyo_TenantId": {
        "type": "string",
        "defaultValue": ""
      },
      "D365_EntityName": {
        "type": "string",
        "defaultValue": "accounts"
      },
      "D365_FilterExpression": {
        "type": "string",
        "defaultValue": ""
      },
      "Admin_Email": {
        "type": "string",
        "defaultValue": ""
      }
    }
  }
}
```

### Flow 2: SharePoint Document Upload to Certyo

This flow triggers when a document is uploaded to a SharePoint library and creates a Certyo record with the document metadata and hash.

```json
{
  "definition": {
    "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
    "contentVersion": "1.0.0.0",
    "triggers": {
      "When_a_file_is_created_in_a_folder": {
        "type": "OpenApiConnectionTrigger",
        "inputs": {
          "host": {
            "connectionName": "shared_sharepointonline",
            "operationId": "OnNewFile",
            "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline"
          },
          "parameters": {
            "dataset": "@parameters('SharePoint_SiteUrl')",
            "folderId": "@parameters('SharePoint_LibraryId')",
            "queryParametersSingleEncoded": true
          }
        },
        "recurrence": {
          "frequency": "Minute",
          "interval": 3
        }
      }
    },
    "actions": {
      "Get_File_Metadata": {
        "type": "OpenApiConnection",
        "inputs": {
          "host": {
            "connectionName": "shared_sharepointonline",
            "operationId": "GetFileMetadataByPath",
            "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline"
          },
          "parameters": {
            "dataset": "@parameters('SharePoint_SiteUrl')",
            "path": "@triggerOutputs()?['headers/x-ms-file-path']"
          }
        },
        "runAfter": {}
      },
      "Get_File_Content": {
        "type": "OpenApiConnection",
        "inputs": {
          "host": {
            "connectionName": "shared_sharepointonline",
            "operationId": "GetFileContentByPath",
            "apiId": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline"
          },
          "parameters": {
            "dataset": "@parameters('SharePoint_SiteUrl')",
            "path": "@triggerOutputs()?['headers/x-ms-file-path']"
          }
        },
        "runAfter": {}
      },
      "Compose_Document_Record": {
        "type": "Compose",
        "inputs": {
          "tenantId": "@parameters('Certyo_TenantId')",
          "database": "sharepoint",
          "collection": "@parameters('SharePoint_LibraryName')",
          "recordId": "@body('Get_File_Metadata')?['UniqueId']",
          "recordPayload": {
            "fileName": "@body('Get_File_Metadata')?['DisplayName']",
            "filePath": "@body('Get_File_Metadata')?['Path']",
            "fileSize": "@body('Get_File_Metadata')?['Size']",
            "contentType": "@body('Get_File_Metadata')?['MediaType']",
            "createdBy": "@body('Get_File_Metadata')?['LastModifiedBy/DisplayName']",
            "createdAt": "@body('Get_File_Metadata')?['Created']",
            "fileContentBase64": "@base64(body('Get_File_Content'))"
          },
          "operationType": "insert",
          "sourceTimestamp": "@body('Get_File_Metadata')?['Created']",
          "idempotencyKey": "@concat('sp:', body('Get_File_Metadata')?['UniqueId'])"
        },
        "runAfter": {
          "Get_File_Metadata": ["Succeeded"],
          "Get_File_Content": ["Succeeded"]
        }
      },
      "Ingest_Document_to_Certyo": {
        "type": "OpenApiConnection",
        "inputs": {
          "host": {
            "connectionName": "shared_certyo",
            "operationId": "IngestRecord",
            "apiId": "/providers/Microsoft.PowerApps/apis/shared_certyo"
          },
          "parameters": {
            "body": "@outputs('Compose_Document_Record')"
          }
        },
        "runAfter": {
          "Compose_Document_Record": ["Succeeded"]
        },
        "retryPolicy": {
          "type": "exponential",
          "count": 4,
          "interval": "PT10S",
          "minimumInterval": "PT5S",
          "maximumInterval": "PT1H"
        }
      }
    },
    "parameters": {
      "Certyo_TenantId": {
        "type": "string",
        "defaultValue": ""
      },
      "SharePoint_SiteUrl": {
        "type": "string",
        "defaultValue": ""
      },
      "SharePoint_LibraryId": {
        "type": "string",
        "defaultValue": ""
      },
      "SharePoint_LibraryName": {
        "type": "string",
        "defaultValue": "Documents"
      }
    }
  }
}
```

### Flow 3: Scheduled Verification Check

This flow runs on a schedule, queries recently ingested records, and verifies their blockchain anchoring status.

```json
{
  "definition": {
    "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
    "contentVersion": "1.0.0.0",
    "triggers": {
      "Recurrence": {
        "type": "Recurrence",
        "recurrence": {
          "frequency": "Minute",
          "interval": 5
        }
      }
    },
    "actions": {
      "Query_Recent_Records": {
        "type": "OpenApiConnection",
        "inputs": {
          "host": {
            "connectionName": "shared_certyo",
            "operationId": "QueryRecords",
            "apiId": "/providers/Microsoft.PowerApps/apis/shared_certyo"
          },
          "parameters": {
            "tenantId": "@parameters('Certyo_TenantId')"
          }
        },
        "runAfter": {}
      },
      "Filter_Unverified": {
        "type": "Query",
        "inputs": {
          "from": "@body('Query_Recent_Records')",
          "where": "@not(equals(item()?['verified'], true))"
        },
        "runAfter": {
          "Query_Recent_Records": ["Succeeded"]
        }
      },
      "Apply_to_each_unverified": {
        "type": "Foreach",
        "foreach": "@body('Filter_Unverified')",
        "actions": {
          "Verify_Record": {
            "type": "OpenApiConnection",
            "inputs": {
              "host": {
                "connectionName": "shared_certyo",
                "operationId": "VerifyRecord",
                "apiId": "/providers/Microsoft.PowerApps/apis/shared_certyo"
              },
              "parameters": {
                "body": {
                  "tenantId": "@items('Apply_to_each_unverified')?['tenantId']",
                  "recordId": "@items('Apply_to_each_unverified')?['recordId']",
                  "recordHash": "@items('Apply_to_each_unverified')?['recordHash']"
                }
              }
            },
            "retryPolicy": {
              "type": "fixed",
              "count": 2,
              "interval": "PT30S"
            }
          },
          "Condition_Verified": {
            "type": "If",
            "expression": {
              "and": [
                {
                  "equals": ["@body('Verify_Record')?['verified']", true]
                }
              ]
            },
            "actions": {
              "Log_Verified": {
                "type": "Compose",
                "inputs": {
                  "status": "verified",
                  "recordId": "@items('Apply_to_each_unverified')?['recordId']",
                  "verifiedAt": "@utcNow()"
                }
              }
            },
            "else": {
              "actions": {
                "Log_Pending": {
                  "type": "Compose",
                  "inputs": {
                    "status": "pending",
                    "recordId": "@items('Apply_to_each_unverified')?['recordId']",
                    "checkedAt": "@utcNow()"
                  }
                }
              }
            },
            "runAfter": {
              "Verify_Record": ["Succeeded"]
            }
          }
        },
        "runAfter": {
          "Filter_Unverified": ["Succeeded"]
        },
        "runtimeConfiguration": {
          "concurrency": {
            "repetitions": 5
          }
        }
      }
    },
    "parameters": {
      "Certyo_TenantId": {
        "type": "string",
        "defaultValue": ""
      }
    }
  }
}
```

## Connector Deployment

### PowerShell: Deploy Custom Connector

```powershell
# Deploy Certyo Custom Connector to Power Platform Environment
# Requires: Microsoft.PowerApps.Administration.PowerShell module

param(
    [Parameter(Mandatory=$true)]
    [string]$EnvironmentId,

    [Parameter(Mandatory=$true)]
    [string]$ConnectorDefinitionPath
)

# Install module if needed
if (-not (Get-Module -ListAvailable -Name Microsoft.PowerApps.Administration.PowerShell)) {
    Install-Module -Name Microsoft.PowerApps.Administration.PowerShell -Force -Scope CurrentUser
}

Import-Module Microsoft.PowerApps.Administration.PowerShell

# Authenticate
Add-PowerAppsAccount

# Read the OpenAPI definition
$connectorDefinition = Get-Content -Path $ConnectorDefinitionPath -Raw

# Create the custom connector
$connector = New-AdminPowerAppCustomConnector `
    -EnvironmentName $EnvironmentId `
    -DisplayName "Certyo Blockchain Authenticity" `
    -Description "Ingest and verify records on Certyo blockchain platform" `
    -OpenApiDefinition $connectorDefinition `
    -IconUri "https://www.certyos.com/icon.png"

Write-Host "Connector created: $($connector.ConnectorId)"
Write-Host "Share with: Set-AdminPowerAppConnectorRoleAssignment"

# Share with all users in the environment (optional)
# Set-AdminPowerAppConnectorRoleAssignment `
#     -EnvironmentName $EnvironmentId `
#     -ConnectorName $connector.ConnectorName `
#     -PrincipalType Group `
#     -RoleName CanViewWithShare `
#     -PrincipalObjectId "everyone"
```

## Platform Limits

| Limit | Value | Mitigation |
|-------|-------|------------|
| Actions per flow run | 600 | Use child flows for complex logic |
| API calls per day (Premium) | 100,000 | Use bulk endpoint to reduce call count |
| Flow run duration | 30 days | Use Recurrence trigger, not long-running |
| Connector timeout | 120 seconds | Certyo responds in <2s; safe margin |
| Concurrent flow runs | 100 | Use concurrency control on Apply to each |
| Retry policy max | 90 retries | Exponential backoff with 4 retries sufficient |

## Verification & Write-back

After Certyo anchors a record (~60-90 seconds), use the scheduled verification flow (Flow 3 above) to check anchoring status and write blockchain proof back to the source system.

The write-back pattern depends on the source:

**Dynamics 365 / Dataverse**: Use the "Update a row" action to set custom columns (certyo_status, certyo_recordhash, certyo_anchor_tx) on the source entity.

**SharePoint**: Use the "Update file properties" action to set metadata columns on the document library item.

**SQL Server**: Use the "Execute a SQL query" action to UPDATE the source table with the verification result.

**Generic HTTP**: Use the HTTP action to PATCH the source system API with the Certyo verification response.

## Code Generation Rules

1. **Use a custom connector (not raw HTTP action) for all Certyo API calls.** Import the OpenAPI 3.0 specification as a custom connector. This provides schema validation, connection reuse, centralized API key management, and discoverability across the organization. Never use the generic HTTP action for Certyo endpoints.

2. **Configure retry policy on every Certyo connector action.** Use exponential backoff with count=4, interval=PT10S, minimumInterval=PT5S, maximumInterval=PT1H. This handles transient network errors and Certyo rate limits without manual error handling flows.

3. **Use Compose actions for payload building, never complex expressions inline.** Build the Certyo record payload in a Compose action with named fields. This makes the flow readable, debuggable, and maintainable. Reference the Compose output in the connector action.

4. **Use Condition actions for response status checking.** After every Certyo API call, add a Condition that checks the status code. Route successes to write-back actions and failures to notification actions. Never assume the API call succeeded.

5. **Keep flows under 600 actions per run.** Power Automate enforces a 600-action limit per flow run. For batch processing, use the bulk endpoint (/api/v1/records/bulk) to ingest up to 1000 records in a single action. For larger datasets, use child flows with pagination.

6. **Use child flows for complex orchestration logic.** When a flow exceeds 20 actions or needs to process multiple record types, extract reusable logic into child flows. Pass parameters via the "Run a Child Flow" action. This improves maintainability and stays within action limits.

7. **Set concurrency control on Apply to each loops.** When verifying multiple records, set the concurrency degree to 5-10 in the Apply to each settings. This balances throughput against Certyo rate limits. Never use unlimited concurrency.

8. **Use flow parameters for all configuration values.** Store tenant IDs, entity names, library paths, and email addresses as flow parameters with default values. Never hard-code configuration in expressions. This enables environment-specific deployment.

9. **Include error notification actions in every flow.** Add a parallel branch on the "Failed" outcome of every Certyo action that sends an email or Teams message to the administrator. Include the record ID, error message, and timestamp for troubleshooting.

10. **Use Recurrence triggers with appropriate intervals for verification polling.** Set verification flows to run every 5 minutes. Use the Filter Array action to select only unverified records before calling the verify endpoint. This minimizes API calls while ensuring timely verification.
