XY Logo
Developer hub

Integration Factory

When the catalog stops, build the missing integration.

Turn public or private API documentation into organization-owned capability contracts. Validation uses an encrypted credential reference, promotion is explicit, and the resulting integration is available to the Planner agent and durable workflow runtime.

Lifecycle

Documentation becomes a live integration deliberately.

One durable run moves through four explicit gates. Discovery and model work continue independently of the requesting HTTP connection; reconnect by run ID or event cursor.
1

Discover

Submit one public HTTPS URL, pasted document, or completed private upload.

2

Generate

Review discovered endpoint names and select at most 20 capability contracts.

3

Test

Execute every required generated capability with a Psono-backed credential reference.

4

Validate

Run structural checks and the factory’s safe automatic runtime probes.

5

Promote

Make the validated and explicitly tested version live only for the API key’s organization.

Access

Separate browsing, building, secrets, and promotion.

Factory generation and promotion are dangerous scopes. Use a dedicated server-side key and grant each capability only to the service that performs it.
OperationRequired scope
Browse integrations, runs, events, and safe connection metadataintegrations:read
Create or validate a service connection outside a factory runintegrations:connect
Upload docs, discover, generate, explicitly test, and validateintegrations:generate
Make a validated and tested version live for your organizationintegrations:promote
Create the write-only secret reference used by validationcredentials:write

Discovery

Start with docs, not credentials.

Supply exactly one documentation source. Public URLs and API base URLs must use HTTPS and pass public-network checks; pasted text is bounded; private files use a signed object-store upload.
Public documentation URL
export XY_API_BASE='https://marketplace.prod.xyai.beer/api/v1'
export XY_API_KEY='xy_prod_…'

DISCOVERY_RESPONSE="$(curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "Idempotency-Key: linear-discovery-v1" \
  --header "Content-Type: application/json" \
  "${XY_API_BASE}/integrations/factory/discoveries" \
  --data '{
    "api_name": "Linear",
    "key": "linear",
    "auth_type": "API_KEY",
    "api_key_header": "Authorization",
    "category": "PROJECT_MANAGEMENT",
    "documentation_url": "https://linear.app/developers/graphql",
    "base_url": "https://api.linear.app",
    "custom_instructions": "Prioritize read-only issue and team operations."
  }')"

RUN_ID="$(printf '%s' "${DISCOVERY_RESPONSE}" | jq -r '.id')"
printf '%s
' "${DISCOVERY_RESPONSE}" | jq
Private document upload
# Reserve a short-lived, organization-scoped upload target.
RESERVATION="$(curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "Content-Type: application/json" \
  "${XY_API_BASE}/integrations/factory/uploads" \
  --data '{
    "file_name": "partner-api.pdf",
    "content_type": "application/pdf"
  }')"

UPLOAD_ID="$(printf '%s' "${RESERVATION}" | jq -r '.id')"
UPLOAD_URL="$(printf '%s' "${RESERVATION}" | jq -r '.upload.url')"
UPLOAD_METHOD="$(printf '%s' "${RESERVATION}" | jq -r '.upload.method')"
UPLOAD_ARGS=()
while IFS=$'	' read -r name value; do
  UPLOAD_ARGS+=(--header "${name}: ${value}")
done < <(printf '%s' "${RESERVATION}" | \
  jq -r '.upload.headers | to_entries[] | [.key, .value] | @tsv')

# Document bytes go directly to object storage, not through the Marketplace API process.
curl --fail-with-body --request "${UPLOAD_METHOD}" \
  "${UPLOAD_ARGS[@]}" \
  --upload-file ./partner-api.pdf \
  "${UPLOAD_URL}"

# Completion verifies the object size and content type. It accepts no body.
curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/integrations/factory/uploads/${UPLOAD_ID}/complete"

# Then start discovery with "upload_id": "${UPLOAD_ID}" instead of documentation_url.

Public URL

XY fetches one HTTPS documentation URL after DNS and network-address validation.

Pasted document

Send a bounded text document in documentation when no file is needed.

Private file

Upload up to 20 MiB using every method and header returned by the reservation.

Observe discovery
# Replay durable events and continue streaming across the lifecycle.
curl --fail-with-body --no-buffer \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "Accept: text/event-stream" \
  "${XY_API_BASE}/integrations/factory/discoveries/${RUN_ID}/events?after=0&stream=true"

# Or poll the safe projection. Wait for awaiting_generation.
RUN="$(curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/integrations/factory/discoveries/${RUN_ID}")"
printf '%s
' "${RUN}" | jq '{status, state_version, discovery}'

Generation

Choose the contracts you intend to operate.

Discovery pauses at awaiting_generation and returns the only valid endpoint selection strings. Omitting endpoints selects the first bounded set; explicit selection makes cost and behavior easier to review.
Generate selected capabilities
# Select exact strings returned in discovery.endpoint_names.
STATE_VERSION="$(printf '%s' "${RUN}" | jq -r '.state_version')"
ENDPOINTS="$(printf '%s' "${RUN}" | jq -c '.discovery.endpoint_names[0:3]')"

GENERATION_RESPONSE="$(
  jq -n --arg discovery_id "${RUN_ID}" --argjson endpoints "${ENDPOINTS}" \
    '{discovery_id: $discovery_id, endpoints: $endpoints}' |
  curl --fail-with-body --request POST \
    --header "Authorization: Bearer ${XY_API_KEY}" \
    --header "If-Match: ${STATE_VERSION}" \
    --header "Content-Type: application/json" \
    "${XY_API_BASE}/integrations/factory/generations" \
    --data-binary @-
)"
printf '%s
' "${GENERATION_RESPONSE}" | jq

# Follow the generation event stream, then GET until status is generated.
curl --fail-with-body --no-buffer \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "Accept: text/event-stream" \
  "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}/events?after=0&stream=true"

Test, validate, and promote

Prove each generated capability with a vault reference.

The credential write is the one trusted secret transfer. XY stores the secret in the organization’s Psono datastore and returns safe metadata; public validation requests and workflow definitions receive only its stable ID.
Create the encrypted credential
# Read the partner token from a protected source. Do not put it in docs or prompts.
: "${PARTNER_API_TOKEN:?Set PARTNER_API_TOKEN from your secret manager}"
CREDENTIAL_RESPONSE="$(
  jq -n --arg password "${PARTNER_API_TOKEN}" '{
    domain: "api.linear.app",
    site_name: "Linear API",
    username: "partner-api-token",
    password: $password
  }' |
  curl --fail-with-body --request POST \
    --header "Authorization: Bearer ${XY_API_KEY}" \
    --header "Content-Type: application/json" \
    "${XY_API_BASE}/credentials" \
    --data-binary @-
)"
unset PARTNER_API_TOKEN

CREDENTIAL_ID="$(printf '%s' "${CREDENTIAL_RESPONSE}" | jq -r '.credential_id')"
printf '%s
' "${CREDENTIAL_RESPONSE}" | jq
Test a generated capability
# Set these per capability and logical test attempt.
CAPABILITY_KEY="list_issues"
TEST_ATTEMPT="1"

# Refresh the generated run before every test attempt.
RUN="$(curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}")"
STATE_VERSION="$(printf '%s' "${RUN}" | jq -r '.state_version')"
printf '%s
' "${RUN}" | jq '{required_capability_test_keys, capability_tests}'

# Supply parameters that satisfy this capability's generated input_schema.
# Repeat this block with a unique attempt for every required capability key.
TEST_RESPONSE="$(
  jq -n --arg capability_key "${CAPABILITY_KEY}" --arg credential_id "${CREDENTIAL_ID}" '{
    capability_key: $capability_key,
    params: {first: 1},
    credential_id: $credential_id,
    confirm_side_effects: false
  }' |
  curl --fail-with-body --request POST \
    --header "Authorization: Bearer ${XY_API_KEY}" \
    --header "Idempotency-Key: factory-${CAPABILITY_KEY}-test-${TEST_ATTEMPT}" \
    --header "If-Match: ${STATE_VERSION}" \
    --header "Content-Type: application/json" \
    "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}/capability-tests" \
    --data-binary @-
)"

TEST_ID="$(printf '%s' "${TEST_RESPONSE}" | jq -r '.id')"
curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}/capability-tests/${TEST_ID}" | jq
Validate and promote
# Refresh first: generation must be generated and every mutation uses the latest version.
RUN="$(curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}")"
STATE_VERSION="$(printf '%s' "${RUN}" | jq -r '.state_version')"

curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "If-Match: ${STATE_VERSION}" \
  --header "Content-Type: application/json" \
  "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}/validations" \
  --data "{"credential_id": "${CREDENTIAL_ID}"}"

# Wait for validated, refresh state_version, then promote. Promotion accepts no body
# and cannot request ALL_ORGS; public promotion is always scoped to this organization.
RUN="$(curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}")"
STATE_VERSION="$(printf '%s' "${RUN}" | jq -r '.state_version')"

curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "If-Match: ${STATE_VERSION}" \
  "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}/promote"

# The terminal projection returns integration, capabilities, and connection_id.
curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/integrations/factory/generations/${RUN_ID}" | jq

Compose

Use the new capability inside a durable workflow.

A completed factory run exposes the promoted integration in the organization catalog. The Planner agent resolves its live capability schemas and active connection when it designs and tests an integration activity.
Start a Planner agent build
# After promotion, the Planner agent sees the live integration and active service connection.
curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "Idempotency-Key: linear-triage-workflow-v1" \
  --header "Content-Type: application/json" \
  "${XY_API_BASE}/planner/builds" \
  --data '{
    "request": "Use the organization Linear integration to fetch open issues for the Support team, normalize priority and assignee, then return one result per issue. Use the exact live capability contract and test the integration call.",
    "context": {
      "inputs": [{"name": "team_name", "type": "string"}],
      "outputs": [{"name": "issues", "type": "array"}]
    }
  }'

REST and MCP

Use the surface that actually supports the operation.

The REST API owns the complete creation path. MCP exposes the safe read and state-transition tools that are useful after an integration already exists.
NeedRESTMCP
Create a brand-new integration or upload private docsSupportedUse REST first
Extend an existing org-owned factory integrationSupportedintegration_factory_extend
Read a run and advance generation, validation, or promotionSupportedintegration_factory_run_get, integration_factory_generate, integration_factory_validate, integration_factory_promote
Read the live integration contractSupportedintegration_get

Operations

Design retries around state, quotas, and side effects.

Factory work consumes model budget and external API capacity. Admission limits active and daily starts per organization, and factory model cost contributes to Marketplace usage.

Idempotency

Use a stable key for each logical discovery attempt. Reuse with a different body is rejected.

Quota

Honor Retry-After on admission or rate-limit responses and inspect GET /usage.

Failure

A failed projection returns a bounded public error. Fix the source or credential, then start a deliberate new run.