Discover
Submit one public HTTPS URL, pasted document, or completed private upload.
The AI native company for Healthcare
Integration Factory
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
Submit one public HTTPS URL, pasted document, or completed private upload.
Review discovered endpoint names and select at most 20 capability contracts.
Execute every required generated capability with a Psono-backed credential reference.
Run structural checks and the factory’s safe automatic runtime probes.
Make the validated and explicitly tested version live only for the API key’s organization.
Access
| Operation | Required scope |
|---|---|
| Browse integrations, runs, events, and safe connection metadata | integrations:read |
| Create or validate a service connection outside a factory run | integrations:connect |
| Upload docs, discover, generate, explicitly test, and validate | integrations:generate |
| Make a validated and tested version live for your organization | integrations:promote |
| Create the write-only secret reference used by validation | credentials:write |
Discovery
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# 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.XY fetches one HTTPS documentation URL after DNS and network-address validation.
Send a bounded text document in documentation when no file is needed.
Upload up to 20 MiB using every method and header returned by the reservation.
# 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
# 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
# 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# 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# 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}" | jqCompose
# 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
| Need | REST | MCP |
|---|---|---|
| Create a brand-new integration or upload private docs | Supported | Use REST first |
| Extend an existing org-owned factory integration | Supported | integration_factory_extend |
| Read a run and advance generation, validation, or promotion | Supported | integration_factory_run_get, integration_factory_generate, integration_factory_validate, integration_factory_promote |
| Read the live integration contract | Supported | integration_get |
Operations
Use a stable key for each logical discovery attempt. Reuse with a different body is rejected.
Honor Retry-After on admission or rate-limit responses and inspect GET /usage.
A failed projection returns a bounded public error. Fix the source or credential, then start a deliberate new run.