This skill is designed to integrate AI models like Claude or Gemini into your Gladly App Platform workflow. By offloading the technical specifics of REST-via-GraphQL configurations to the AI, you can streamline the process of creating, modifying, and testing integrations. Use this to let the model handle the underlying schema complexity, allowing you to focus on the broader application logic.
Overview
App Platform Apps retrieve data and take actions in external systems using REST APIs. Configuration uses Go text templates for generating REST requests and transforming responses. APIs are exposed via GraphQL Query and Mutation operations.
Key Documentation References
Directory Structure
{app-name}/
├── manifest.json # App metadata with semver version
├── authentication/
│ ├── headers/ # Auth header templates (*.gtpl)
│ ├── request_signing/ # Request signing header templates
│ └── oauth/
│ ├── config.json # OAuth grant type config
│ ├── auth_code/ # Authorization code request
│ ├── access_token/ # Access token request
│ └── refresh_token/ # Token refresh request
├── actions/
│ ├── actions_schema.graphql # GraphQL schema for actions
│ └── {action-name}/
│ ├── config.json # HTTP method, content type
│ ├── request_url.gtpl # URL template
│ ├── request_body.gtpl # Body template (if needed)
│ └── response_transformation.gtpl # Response transformation
└── data/
├── data_schema.graphql # GraphQL schema for data pulls
└── pull/{data-pull-name}/
├── config.json # HTTP method, data type, dependencies
├── request_url.gtpl # URL template
├── request_body.gtpl # Body template (if needed)
├── response_transformation.gtpl # Response transformation
├── external_id.gtpl # Extract unique ID
├── external_updated_at.gtpl # Extract last updated timestamp
└── external_parent_id.gtpl # Extract parent ID (if applicable)
Go Template Guidelines
Range Actions
Reference root context with $ inside range loops:
{{ range $i, $customer := .customers}}
Customer {{ $i }} of {{ len $.customers }}
Name: {{ $customer.firstName }} {{ $customer.lastName }}
{{ end }}
Sprig Function Notes
contains: First param is substring, second is string to search:{{ contains "hello" "hello world" }}returnstrueDate functions use Go time layout format (see time package constants)
Authentication Header Context
{
"integration": {
"configuration": {}, // App config including credentials
"secrets": {} // Sensitive credentials
}
}
OAuth Configuration
Grant Types
authorization_code- RequiresauthDomainin config.jsonclient_credentialspassword
Each OAuth request type (auth_code, access_token, refresh_token) has:
config.json- HTTP method and content typerequest_url.gtpl- URL templaterequest_body.gtpl- Body template (for POST)headers/- Optional request-specific headers
Actions
Actions perform operations in external systems (cancel order, create return, etc.).
config.json
{
"httpMethod": "GET|POST|PUT|PATCH|DELETE",
"contentType": "application/json", // Omit for GET
"rawResponse": false // Set true for non-standard content types
}
Template Context
{
"integration": { "configuration": {}, "secrets": {} },
"inputs": {} // GraphQL field input parameters
}
GraphQL Schema (actions_schema.graphql)
Use
@action(name: "action-name")directive to link fields to actionsDefine types before referencing them (no forward declarations)
No union types supported
Use
DateTimescalar for ISO8601 timestampsUse
Currencyscalar for currency stringsAdd descriptions for non-obvious fields using
""" description """
Response Transformation
Output must conform to GraphQL return type
Handle non-success status codes (except 401/403 auth errors)
Use
failfunction for unexpected errorsConvert timestamps to ISO8601 format
Data Pulls
Data pulls retrieve data from external systems.
config.json
{
"dataType": { "name": "order", "version": "1.0" },
"dependsOnDataTypes": ["customer"], // Execute after these pulls
"httpMethod": "GET|POST",
"contentType": "application/json", // Omit for GET
"rawResponse": false
}
Template Context
{
"integration": { "configuration": {}, "secrets": {} },
"customer": {
"id": "gladly-customer-id",
"name": "John Smith",
"primaryEmailAddress": "john@email.com",
"emailAddresses": ["john@email.com"],
"primaryPhoneNumber": { "number": "+15551234567", "type": "MOBILE" },
"phoneNumbers": [{ "number": "+15551234567", "type": "MOBILE" }]
},
"externalData": {
"dependent_type_name": [] // Data from dependent pulls
}
}
Multiple Requests
Multiple URLs - Output each URL on a new line:
{{- range .customer.emailAddresses -}}
https://api.example.com/search?email={{urlquery .}}
{{ end -}}
Multiple Bodies - Use #body marker:
{{- range .customer.emailAddresses -}}
#body
{ "emailAddress": "{{.}}" }
{{ end -}}
GraphQL Schema (data_schema.graphql)
Use
@dataType(name: "type-name", version: "1.0")directiveDefine types before referencing them
Use
DateTimeandCurrencyscalars (provided by runtime)Add descriptions for non-obvious fields
Parent/Child Relationships
Parent references child IDs - Use @childIds:
type Customer @dataType(name: "customer", version: "1.0") {
customerId: ID!
orders: [Order] @childIds(template: "{{.orderIds | join ','}}")
}
Child references parent ID - Use @parentId and external_parent_id.gtpl:
type Customer @dataType(name: "customer", version: "1.0") {
id: ID!
orders: [Order] @parentId(template: "{{.id}}")
}
Metadata Templates
external_id.gtpl- Extract unique ID:{{- .orderId -}}external_updated_at.gtpl- Extract ISO8601 timestampexternal_parent_id.gtpl- Extract parent ID for relationshipsUse
int64for numeric IDs:{{- int64 .reviewId -}}Trim whitespace in output
Response Transformation
Return empty array
[]when no dataConvert timestamps to ISO8601
Don't check statusCode unless
rawResponse: true(platform handles non-200)
Testing
Test Directory Structure
{template-directory}/
└── _test_/
├── integration.json # Shared test data (inherited)
├── {test-dataset-name}/
│ ├── rawData.json # Input: response data
│ ├── customer.json # Input: customer data
│ ├── inputs.json # Input: action inputs
│ ├── externalData.json # Input: dependent data
│ ├── expected_request_url.txt
│ ├── expected_request_body.json
│ └── expected_response_transformation.json
└── {another-test-dataset}/
└── ...
Test Input Files
Named after context fields: rawData.json, customer.json, integration.json, inputs.json, externalData.json
Expected Output Files
Template | Expected File | Format |
|---|---|---|
request_url.gtpl | expected_request_url.txt | Text |
request_body.gtpl | expected_request_body.* | json/txt/bin |
response_transformation.gtpl | expected_response_transformation.json | JSON |
{header}.gtpl | expected_{header}.txt | Text |
external_id.gtpl | expected_external_id.txt | Text (one per line) |
external_updated_at.gtpl | expected_external_updated_at.txt | Text |
external_parent_id.gtpl | expected_external_parent_id.txt | Text |
Test Features
Test data files in
_test_/are inherited by subdirectoriesExpected
.txtfiles can use regex by wrapping content in/regex/For
fail/stopfunction tests, expected output is JSON string of the messageMetadata templates test against arrays in
externalData.json, output one value per line
Best Practices
Version Semantically - Major for breaking changes, minor for features, patch for fixes
Add Field Descriptions - Document non-obvious fields in GraphQL schemas
Handle Errors - Transform error responses appropriately, use
failfor unexpected errorsTimestamps - Always convert to ISO8601 format, use
Zfor UTCTest Coverage - Create test datasets for all conditional code paths
Template Safety - Check field presence before referencing optional customer data