← Files CrowdStrike Falcon FoundryARCHIVED FILE
skills/api-integrations/references/calling-patterns.md
9.67 KB · Oct 9, 2026 · 00:07 UTC
# Calling API Integrations from Code
Full examples for calling API integrations from UI extensions, Python functions, and Go functions.
## UI Extensions (JavaScript / Foundry-JS)
UI extensions call API integrations through `falcon.apiIntegration()`, not direct HTTP calls. Foundry UI extensions run in sandboxed iframes and cannot make arbitrary HTTP requests.
```javascript
// React example using Foundry-JS
import FalconApi from '@crowdstrike/foundry-js';
const falcon = new FalconApi();
await falcon.connect();
// Call an API integration operation
const apiIntegration = falcon.apiIntegration({
definitionId: 'Okta', // Matches the API integration name in manifest.yml
operationId: 'listUsers' // Matches the operationId in the OpenAPI spec
});
const response = await apiIntegration.execute({
request: { params: {} } // Pass query parameters here
});
// Response structure:
// response.resources[0].status_code - HTTP status from the external API
// response.resources[0].response_body - Parsed response body
const resource = response.resources?.[0];
const statusCode = resource?.status_code;
const body = resource?.response_body;
```
**Reference:** See `foundry-sample-foundryjs-demo` for a working example of `falcon.apiIntegration().execute()`.
## Python Functions (FalconPy APIIntegrations)
```python
# functions/external-enrichment/main.py
from logging import Logger
from typing import Any, Dict, Union
from crowdstrike.foundry.function import Function, Request, Response
from falconpy import APIIntegrations
func = Function.instance()
@func.handler(method='POST', path='/api/enrich')
def enrich_with_external_api(request: Request, config: Union[Dict[str, Any], None], logger: Logger) -> Response:
"""Call an external API via API Integration."""
# Get the data to enrich
ip_address = request.body.get("ip_address")
if not ip_address:
return Response(body={"error": "IP address required"}, code=400)
# Use API Integrations SDK to call the configured integration
api_integrations = APIIntegrations()
# Call the API integration operation
# definition_id is the integration name from manifest.yml
response = api_integrations.execute(
definition_id="VirusTotal", # Integration name
operation_id="getIpReport", # Operation ID from OpenAPI spec
body={
"request": {
"params": {
"ip": ip_address
}
}
}
)
if response["status_code"] != 200:
logger.error(f"API integration failed: {response.get('errors')}")
return Response(
body={"error": "Failed to call external API"},
code=500
)
# Extract the external API response
resources = response.get("body", {}).get("resources", [])
if not resources:
return Response(body={"error": "No data returned"}, code=404)
external_data = resources[0].get("response_body", {})
return Response(
body={
"ip_address": ip_address,
"enrichment": external_data
},
code=200
)
if __name__ == '__main__':
func.run()
```
## Go Functions (gofalcon)
```go
// functions/external-enrichment/main.go
package main
import (
"context"
"encoding/json"
"log/slog"
"github.com/crowdstrike/gofalcon/falcon"
"github.com/crowdstrike/gofalcon/falcon/client"
"github.com/crowdstrike/gofalcon/falcon/client/custom_storage"
fdk "github.com/crowdstrike/foundry-fn-go"
)
type enrichRequest struct {
IPAddress string `json:"ip_address"`
}
type enrichResponse struct {
IPAddress string `json:"ip_address"`
Enrichment map[string]interface{} `json:"enrichment"`
}
func newHandler(_ context.Context, _ *slog.Logger, _ fdk.SkipCfg) fdk.Handler {
m := fdk.NewMux()
m.Post("/api/enrich", fdk.HandleFnOf(func(ctx context.Context, r fdk.RequestOf[enrichRequest]) fdk.Response {
if r.Body.IPAddress == "" {
return fdk.Response{
Code: 400,
Body: fdk.JSON(map[string]string{"error": "IP address required"}),
}
}
// Get Falcon client for API Integrations
accessToken := r.Header.Get("X-CS-ACCESSTOKEN")
opts := fdk.FalconClientOpts()
falconClient, err := falcon.NewClient(&falcon.ApiConfig{
AccessToken: accessToken,
Cloud: falcon.Cloud(opts.Cloud),
Context: ctx,
UserAgentOverride: opts.UserAgent,
})
if err != nil {
return fdk.Response{
Code: 500,
Body: fdk.JSON(map[string]string{"error": "Failed to authenticate"}),
}
}
// Call API integration
// Note: Use the integration's definition_id from manifest.yml
integrationRequest := map[string]interface{}{
"request": map[string]interface{}{
"params": map[string]string{
"ip": r.Body.IPAddress,
},
},
}
requestBody, _ := json.Marshal(integrationRequest)
// Execute API integration operation
params := custom_storage.NewExecuteCommandParamsWithContext(ctx)
params.SetDefinitionID("VirusTotal") // Integration name from manifest.yml
params.SetOperationID("getIpReport") // Operation ID from OpenAPI spec
params.SetBody(requestBody)
apiResponse, err := falconClient.CustomStorage.ExecuteCommand(params)
if err != nil {
return fdk.Response{
Code: 500,
Body: fdk.JSON(map[string]string{"error": "Failed to call external API"}),
}
}
// Parse the response
var result struct {
Resources []struct {
StatusCode int `json:"status_code"`
ResponseBody map[string]interface{} `json:"response_body"`
} `json:"resources"`
}
if err := json.Unmarshal(apiResponse.Payload, &result); err != nil {
return fdk.Response{
Code: 500,
Body: fdk.JSON(map[string]string{"error": "Failed to parse response"}),
}
}
if len(result.Resources) == 0 {
return fdk.Response{
Code: 404,
Body: fdk.JSON(map[string]string{"error": "No data returned"}),
}
}
response := enrichResponse{
IPAddress: r.Body.IPAddress,
Enrichment: result.Resources[0].ResponseBody,
}
return fdk.Response{
Code: 200,
Body: fdk.JSON(response),
}
}))
return m
}
func main() {
fdk.Run(context.Background(), newHandler)
}
```
## Request Schema Validation Happens Before the Proxy Call
The platform validates the `request` object against the OpenAPI spec's parameter schemas before it forwards anything to the vendor. Values must be scalars typed exactly as the spec declares them — an `integer` parameter takes `1`, not `"1"` and not `["1"]`. A mismatch never reaches the vendor; it comes back as:
```
400 request failed schema validation: /properties/params/properties/query/properties/limit ...
```
This trips up two habits. The foundry-js `Params` type suggests `query?: Record<string, string[]>`, and the FDK's own `request.params.query` is `Dict[str, List[str]]` — both list-valued shapes fail for anything other than `type: string` array parameters. Unwrap and convert before forwarding:
```python
# Python — forward a query param from the handler's request to an integration
limit = int(request.params.query.get("limit", ["25"])[0])
response = api.execute_command_proxy(body={"resources": [{
"definition_id": "VendorApi",
"operation_id": "listItems",
"request": {"params": {"query": {"limit": limit}}}, # scalar, typed per the spec
}]})
```
```javascript
// foundry-js — same rule
await apiIntegration.execute({ request: { params: { query: { limit: 25 } } } }); // number, not '25' or ['25']
```
Path parameters go under `params.path`, query parameters under `params.query`, and headers under `headers`; a request body goes in `json`. Check each parameter's `schema.type` in the spec before building the request.
## Extracting Fields from API Responses
Before writing code that reads fields from an API response, verify the field's location in the OpenAPI spec's response schema. Fields are often nested under objects (e.g., `meta.severity` not `severity`). Similarly, query parameter names may differ from response field names (e.g., filtering by `meta.severity` even though the CSV column is just `severity`).
**Checklist:**
1. Find the endpoint's response schema in the OpenAPI spec
2. Trace the field path — is it top-level or nested under an object?
3. For query parameters, check the `parameters` section for the exact name
4. Match your struct/dict access to the actual nesting (e.g., `ioc.get("meta", {}).get("severity", '')`)
## Key Points
| Aspect | Detail |
|--------|--------|
| **Python SDK** | `APIIntegrations()` from FalconPy with `execute()` method |
| **Go SDK** | Falcon client with `CustomStorage.ExecuteCommand()` |
| **definition_id** | Must match the integration name in `manifest.yml` |
| **operation_id** | Must match the `operationId` in the OpenAPI spec |
| **Authentication** | Automatic when called from within FDK handlers |
| **Response structure** | Returns `resources` array with `status_code` and `response_body` |
| **Request validation** | Validated against the spec's parameter schemas before proxying — scalars typed per the spec, not string arrays |
| **Local development** | Use the UUID `definition_id` from `manifest.yml`, not the human-readable name (avoids 404 errors) |
SHA-256: cea164de12c95f2608cd167237f37a3b62a69a1cdff40ee0c166e58168ab161d