Envelope
Developer Docs
REST API
v1 · Pre-release

API Reference

Reference for the Envelope core API. Manage templates and deployments programmatically.

Authentication

Most Envelope endpoints require an API key. Authenticate your requests by providing the key in the Authorization header.

Authorization: Bearer env_live_xxxxxxxxxxxxxx

Personal keys

Tied to your account. Can create and manage templates you personally own. Generated from Account → API Keys.

Org keys

Tied to an organisation, not any individual. Can manage all templates owned by that org. Generated from Org dashboard → API Keys. Use these for CI/CD and shared automation so access doesn't break when people leave.

Copy your key for use in examples

Set it in your shell: export ENVELOPE_KEY=<paste here>

Errors

Envelope uses conventional HTTP response codes to indicate the success or failure of an API request.

2xx
Success

The request was successfully received, understood, and accepted.

4xx
Client Errors

The request contains bad syntax or cannot be fulfilled (e.g. missing auth).

5xx
Server Errors

The server failed to fulfill a valid request.

All error responses return a JSON body with a single error field:

// 4xx / 5xx error body
{ "error": "Install not found" }

Rate Limits

All API endpoints are rate-limited per IP address. Requests that exceed the limit receive a 429 Too Many Requests response. The Retry-After header indicates how many seconds to wait before retrying.

ScopeLimitWindow
All endpoints (global)300 requests15 minutes
Auth routes (/api/auth/*)20 requests15 minutes
Public template generate (/api/templates/generate/public)5 requests1 hour per IP

Limits apply per IP. If you need higher throughput for production automation, contact us.

GET

List Templates

Returns a list of published templates available in the registry. No authentication required — the registry is public.

GET /api/templates
Public
curl -X GET https://openenvelope.org/api/templates

Response

{
  "templates": [
    {
      "id": "tmpl_xyz",
      "slug": "sales-team-v1",
      "name": "Outbound Sales Team",
      "description": "Standard B2B SDR configuration",
      "ownerTeam": "acme-corp",
      "visibility": "public",
      "status": "published",
      ...
    }
  ]
}
POST

Create Template

Creates a new template in draft mode.

POST /api/templates
Auth Required
curl -X POST https://openenvelope.org/api/templates \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "support-squad",
    "name": "L1 Support Squad",
    "description": "Handles inbound tickets",
    "ownerTeam": "customer-success",
    "visibility": "team",
    "definition": {
      "requiredVariables": ["companyName"],
      "requiredSecrets": ["ZENDESK_API_KEY"],
      "agents": [
        {
          "key": "triage",
          "name": "Triage Agent",
          "title": "L1 Triage Agent",
          "role": "support",
          "capabilities": "Handles inbound ticket classification and initial response.",
          "model": "anthropic:claude-haiku-3-5",
          "prompt": "You are a triage agent for {{companyName}}..."
        }
      ]
    }
  }'

Variables and secrets

requiredVariables — non-sensitive config values interpolated into agent prompts at install time via {{varName}} syntax (e.g. "companyName", "supportEmail"). They are baked into the prompt before deployment — not injected at runtime.

requiredSecrets — API keys or tokens the deployer must supply (e.g. "ZENDESK_API_KEY", "SLACK_BOT_TOKEN"). Stored in Paperclip's encrypted secrets store. For local adapters they are injected as env vars into agent processes. For http managed agents they are injected as ${SECRET_NAME} substitution in any URL or header the managed runtime sends.

Both arrays are optional — omit them if your team needs no external configuration. Envelope's AI scaffolding automatically populates these when you describe a team that references specific tools.

Model selection — one team, any platform

Each agent declares a model in provider:model format — the same field works across every platform. Envelope translates it automatically: Paperclip picks the right local runtime (claude_local, codex_local, gemini_local), Relevance AI sets its llm_config — future platforms do their own translation.

Supported values: anthropic:claude-opus-4-5, anthropic:claude-sonnet-4-5, anthropic:claude-haiku-3-5, openai:gpt-4o, openai:o4-mini, openai:codex-mini-latest, google:gemini-2.5-pro, google:gemini-2.0-flash.

For Paperclip only: agents can also declare "adapterType": "http" to use a webhook runtime instead of a local CLI — this is the only case where adapterType needs to be set explicitly. In all other cases it is auto-derived from the model field.

Distribution settings

Three optional top-level fields control how a public team is distributed. They are only meaningful when visibility is "public":

FieldTypeDescription
pricingModelstring"free", "per_run", or "per_k_tokens". Defaults to "free".
priceUsdCentsintegerPrice in US cents per run or per 1K tokens. Required when pricingModel is not "free".
forkablebooleanWhether other builders can fork this team into their own workspace. Defaults to true. Owners can always fork their own teams regardless.

Access Policy Schema

The accessPolicy field on any agent definition controls which outbound HTTP requests that agent is allowed to make. It travels with the team definition to every platform — part of Envelope's portable standard for agent access controls.

Enforcement scope

Access policies are enforced by Envelope's managed runtime (agents using adapterType: "http"). For self-hosted Paperclip deployments the policy travels in the team config and can be enforced at the operator's network layer. The field is ignored by platforms that don't yet support it — it never breaks existing deployments.

Agent definition with accessPolicy
{
  "key": "support-agent",
  "name": "Support Agent",
  "model": "anthropic:claude-haiku-3-5",
  "prompt": "...",
  "accessPolicy": {
    "accessPolicyVersion": "1",
    "defaultAction": "allow",
    "rules": [
      {
        "match": { "host": "api.zendesk.com", "methods": ["GET"] },
        "action": "allow"
      },
      {
        "match": { "host": "api.zendesk.com", "methods": ["DELETE"] },
        "action": "deny",
        "reason": "Ticket deletion is irreversible — disabled for all agents."
      },
      {
        "match": { "host": "*.internal.co" },
        "action": "deny",
        "reason": "Internal endpoints must never be reachable from agents."
      }
    ]
  }
}
FieldTypeDescription
accessPolicyVersionstringAlways "1". Required when the field is present.
defaultActionstringWhat to do when no rule matches. "allow" (default) or "deny".
rulesRule[]Ordered list of rules. Evaluated top-to-bottom. First matching rule wins.

Rule object

FieldTypeDescription
match.hoststringHostname to match. Supports leading wildcard: *.zendesk.com matches api.zendesk.com. Omit to match all hosts.
match.methodsstring[]HTTP methods to match, e.g. ["DELETE", "PATCH"]. Omit to match all methods.
match.pathPrefixstringURL path prefix to match, e.g. /api/v2/tickets. Omit to match all paths.
actionstring"allow" proceeds immediately. "deny" blocks with an error. "require_approval" routes the request to the LLM security judge — allowed or blocked based on the agent's declared role and the request context.
reasonstringHuman-readable explanation. Shown in block messages and activity logs. Optional but recommended for deny rules.

Common patterns

Read-only access to an API: Set defaultAction: "allow", add a deny rule for methods: ["POST","PUT","PATCH","DELETE"].

Allowlist only specific domains: Set defaultAction: "deny", add allow rules for each permitted host.

Block a specific endpoint: Add a deny rule with match.host + match.pathPrefix targeting the sensitive path. Leave everything else on defaultAction: "allow".

POST

Publish Template

Transitions a draft to published status. If the draft has a parentTemplateId (i.e. it's a version draft), the content is merged into the parent template, the parent's version is incremented, and the draft row is deleted — the parent's id and slug never change.

POST /api/templates/:id/publish
Auth Required
curl -X POST https://openenvelope.org/api/templates/tmpl_abc123/publish \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Response

{
  "id": "tmpl_abc123",
  "slug": "support-squad",
  "name": "L1 Support Squad",
  "status": "published",
  "publishedAt": "2025-04-03T12:00:00.000Z"
}
POST

New Version

Creates a version draft linked to an existing published template. Edit the draft freely — it has no impact on live installs until you publish it.

POST /api/templates/:id/new-version
Auth Required
curl -X POST https://openenvelope.org/api/templates/tmpl_abc123/new-version \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Response

{
  "id": "tmpl_draft_v2",
  "parentTemplateId": "tmpl_abc123",
  "slug": "support-squad-v2",
  "status": "draft",
  "version": 2,
  ...
}

Publishing a version draft

When you call POST /api/templates/:draftId/publish on a version draft, Envelope detects the parentTemplateId and merges the draft into the parent — copying the updated definition, bumping the version, and deleting the draft row. The parent template's id and slug never change.

DELETE

Delete Template

Permanently deletes a team. Allowed for drafts (any kind) and published templates with no active installs. Returns 409 if the team has active installs — archive it instead.

DELETE /api/templates/:id
Auth Required
curl -X DELETE https://openenvelope.org/api/templates/tmpl_abc123 \
  -H "Authorization: Bearer $ENVELOPE_KEY"
204
Deleted

Empty response — the template has been permanently removed.

409
Has installs

Returns canArchive: true. Use the archive endpoint to delist instead.

POST

Archive Template

Delists a published team from the marketplace and blocks new installs. Existing installs are unaffected — they continue running. Once all installs are removed, the team can be hard deleted.

POST /api/templates/:id/archive
Auth Required
curl -X POST https://openenvelope.org/api/templates/tmpl_abc123/archive \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Response

{
  "id": "tmpl_abc123",
  "slug": "support-squad",
  "status": "archived",
  "installCount": 4,
  ...
}
Only published templates can be archived. Drafts should be deleted directly.
POST

Rollback Template

Creates a new version draft pre-loaded with the definition from a specific past version. Non-destructive — the live published version continues running while you review and publish the restored draft.

POST /api/templates/:id/rollback
Auth Required
curl -X POST https://openenvelope.org/api/templates/tmpl_abc123/rollback \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "version": 2 }'

Body

FieldTypeDescription
versionintegerThe version number to restore from (must have a stored snapshot)

Response — 201 Created

{
  "id": "tmpl_def456",
  "slug": "support-squad",
  "status": "draft",
  "version": 4,
  "parentId": "tmpl_abc123",
  ...
}

Returns 422 if the requested version has no stored snapshot. Snapshots are only available for versions published after the rollback feature was introduced.

Returns 409 if the team already has an active version draft — publish or delete the existing draft first.

POST

Fork Template

Creates a new private draft in your workspace that is a copy of the specified published team — including all agents, prompts, and settings. Use this to start from a known-good community design and customise from there. Returns 403 if the source team has forkable: false and you are not the owner.

POST /api/templates/:id/fork
Auth Required
curl -X POST https://openenvelope.org/api/templates/tmpl_abc123/fork \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Response — 201 Created

{
  "id": "tmpl_fork789",
  "slug": "support-squad-fork",
  "name": "L1 Support Squad (fork)",
  "status": "draft",
  "visibility": "team",
  "forkedFromId": "tmpl_abc123",
  ...
}

Returns 403 if the source team's forkable flag is false and the caller is not the owner.

Returns 404 if the source template does not exist or is not published.

POST

Test Chat

Streams a chat response from the root agent of a team definition. Accepts the full definition object and a conversation history array — useful for testing prompts interactively without publishing. Returns a streaming SSE response; no install required.

POST /api/templates/test-chat
Auth Required
curl -X POST https://openenvelope.org/api/templates/test-chat \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "definition": { ... },
    "messages": [
      { "role": "user", "content": "I need help resetting my password" }
    ]
  }'

Body

FieldTypeDescription
definitionobject requiredThe full team definition object. The root agent's model and prompt are used for the chat response.
messagesarray requiredConversation history as [{ role: "user" | "assistant", content: string }]. Must contain at least one message. The last message is sent to the agent; prior messages are passed as context.

Response — SSE stream

Returns a streaming SSE response identical in format to the generate and assist endpoints. Text chunks are streamed as they are produced; the final event carries done: true.

data: {"chunk": "Hi! I can help you reset your password."}
data: {"chunk": " Could you confirm the email address on your account?"}
data: {"done": true}

Model: Test Chat uses the root agent's model field from the supplied definition, defaulting to anthropic:claude-haiku-4-5 if none is set.

Secrets: No deployer secrets are injected during test runs — secret references in the prompt are passed as-is. If your prompt requires live credential substitution, test via a real Paperclip install instead.

POST

Generate from Description

Generates a complete team definition from a plain-English description. Returns a streaming SSE response — each event is a JSON chunk of the definition as it is produced. The final event contains the complete definition and a done: true flag. The authenticated endpoint accepts a model parameter; the public /api/templates/generate/public endpoint hardcodes anthropic:claude-haiku-4-5.

POST /api/templates/generate
Auth Required
curl -X POST https://openenvelope.org/api/templates/generate \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "A sales development team with an SDR manager, two outbound SDRs who work leads in HubSpot, and a RevOps analyst who tracks pipeline metrics.",
    "model": "anthropic:claude-sonnet-4-6"
  }'

Body

FieldTypeDescription
descriptionstring requiredPlain-English description of the team to generate.
modelstringModel to use for generation. Default: openai:gpt-5-mini. Also accepts anthropic:claude-sonnet-4-6, anthropic:claude-haiku-4-5, openai:gpt-4o, openai:gpt-4o-mini.

Response — SSE stream

Each event carries a partial or complete JSON object. The final event has done: true and the full definition.

data: {"chunk": "{\n  \"version\": \"v1\""}
data: {"chunk": ",\n  \"description\": \"Sales Development Team\""}
...
data: {
  "done": true,
  "definition": {
    "version": "v1",
    "description": "Sales Development Team ...",
    "requiredVariables": ["companyName"],
    "requiredSecrets": ["HUBSPOT_API_KEY"],
    "agents": [
      {
        "key": "sdr-manager",
        "name": "SDR Manager",
        "title": "Head of Sales Development",
        "role": "manager",
        "capabilities": "Manages outbound SDRs and reviews pipeline metrics.",
        "model": "anthropic:claude-sonnet-4-5",
        "prompt": "You are the SDR Manager at {{companyName}}..."
      },
      ...
    ]
  }
}

Rate limit — public endpoint

The unauthenticated POST /api/templates/generate/public endpoint (used by the /try page) is limited to 5 requests per hour per IP and always uses anthropic:claude-haiku-4-5. Authenticated requests via /api/templates/generate are not rate-limited.

POST

AI Assist

Applies a natural language instruction to an existing team definition and returns an updated definition. Used by the AI Assist panel in the team editor. Like the generate endpoint, the response is a streaming SSE event stream — the final event contains the updated definition and a diff summary.

POST /api/templates/assist
Auth Required
curl -X POST https://openenvelope.org/api/templates/assist \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "currentDefinition": { ... },
    "instruction": "Add a Slack notification agent that pings #ops whenever a deal is stalled.",
    "model": "anthropic:claude-sonnet-4-6"
  }'

Body

FieldTypeDescription
currentDefinitionobject requiredThe team definition to modify. Must be a valid Envelope team definition object.
instructionstring requiredPlain-English instruction describing the change to apply.
modelstringModel to use. Default: anthropic:claude-sonnet-4-6. Also accepts anthropic:claude-haiku-4-5, openai:gpt-4o, openai:gpt-4o-mini.
conversationHistoryarrayOptional prior turns as [{ role: "user" | "assistant", content: string }]. Pass the previous exchanges to give the model context for follow-up instructions.

Response — SSE stream

The endpoint has two modes depending on whether the model interpreted the instruction as an apply or a discuss request. In APPLY mode the final event carries definition. In DISCUSS mode it carries text. Always check which key is present before processing.

data: {"chunk": "{\n  \"teamName\": \"Sales Dev Team\""}
...
// APPLY MODE — model returned JSON: final event carries the updated definition
data: {"done": true, "definition": { ... }}

// DISCUSS MODE — model responded in text: final event carries plain text
data: {"done": true, "text": "I'd suggest adding a Slack notification agent ..."}

Non-destructive by design

Assist returns an updated definition but does not save or publish it automatically. The editor's AI Assist panel shows the diff and lets the builder apply or discard the change before committing. If you call this endpoint directly, you are responsible for saving the result via Create Template or a form save action.

GET

List Platforms

Returns the list of orchestration platforms Envelope currently supports, along with the credential fields required to deploy to each one. Use this to dynamically build your deployment UI without hardcoding platform details.

GET /api/platforms
Public
curl https://openenvelope.org/api/platforms

Response

{
  "platforms": [
    {
      "id": "paperclip",
      "label": "Paperclip",
      "credentialFields": [
        { "key": "paperclipBaseUrl", "label": "Base URL",  "type": "text",     "required": true },
        { "key": "paperclipApiKey",  "label": "API Key",   "type": "password", "required": true }
      ],
      "capabilities": {
        "maxHierarchyDepth": null,
        "supportedModels":   null,
        "billingCompleteness": "full",
        "provisioningModel": "rest"
      }
    },
    {
      "id": "relevance_ai",
      "label": "Relevance AI",
      "credentialFields": [
        { "key": "projectId", "label": "Project ID", "type": "text",     "required": true },
        { "key": "apiKey",    "label": "API Key",    "type": "password", "required": true },
        { "key": "region",    "label": "Region",     "type": "text",     "required": true }
      ],
      "capabilities": {
        "maxHierarchyDepth": null,
        "supportedModels":   null,
        "billingCompleteness": "none",
        "provisioningModel": "rest"
      }
    },
    {
      "id": "bedrock",
      "label": "Amazon Bedrock",
      "credentialFields": [
        { "key": "accessKeyId",          "label": "Access Key ID",      "type": "text",     "required": true },
        { "key": "secretAccessKey",      "label": "Secret Access Key",  "type": "password", "required": true },
        { "key": "region",               "label": "Region",             "type": "text",     "required": true },
        { "key": "agentResourceRoleArn", "label": "Agent IAM Role ARN", "type": "text",     "required": true }
      ],
      "capabilities": {
        "maxHierarchyDepth": 1,
        "supportedModels": ["claude", "anthropic", "titan", "nova", "llama", "mistral", "cohere", "ai21", "jamba", "deepseek"],
        "billingCompleteness": "full",
        "provisioningModel": "rest"
      }
    }
  ]
}
POST

Create Install

Deploys a template to a target orchestration platform. Specify the platform field and the matching credentials — Envelope provisions all agents according to the template definition. Defaults to paperclip if omitted.

POST /api/installs
Auth Required
# Envelope Managed — no external platform needed
curl -X POST https://openenvelope.org/api/installs \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "tmpl_abc123",
    "platform":   "envelope",
    "variables":  { "companyName": "Acme Corp" }
  }'
# Secrets are NOT required here — add them afterwards:
# PATCH /api/installs/{installId}/secrets

# Relevance AI
curl -X POST https://openenvelope.org/api/installs \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "tmpl_abc123",
    "platform": "relevance_ai",
    "credentials": {
      "projectId": "proj_...",
      "apiKey":    "sk-...",
      "region":    "f1db6c"
    },
    "variables": { "companyName": "Acme Corp" }
  }'

# Paperclip (self-hosted)
curl -X POST https://openenvelope.org/api/installs \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "tmpl_abc123",
    "platform": "paperclip",
    "companyId": "org_789",
    "credentials": {
      "paperclipBaseUrl": "https://paperclip.ing/api",
      "paperclipApiKey":  "pc_admin_key_..."
    },
    "variables": { "companyName": "Acme Corp" }
  }'

Envelope Managed — how it differs

When platform: "envelope" is used, Envelope itself is the runtime — no external infrastructure is provisioned. This has several important differences from other platforms:

  • ·No credentials required. There is no external platform to authenticate against — omit the credentials field entirely.
  • ·Secrets are added post-install. Required secrets are not validated at install time. Add them after creation via PATCH /installs/:id/secrets.
  • ·API key is automatically scoped. The API key used to create the install is immediately linked to the new installId — so GET /installs/whoami returns it right away, no extra setup needed.
  • ·Billing is native to Envelope. Usage (runs, tokens) is tracked and billed directly through Envelope's billing cycle — no external platform billing. Attach stripeCustomerId and stripePaymentMethodId at install time (or via the Card Setup Intent flow) so Envelope can charge at end of cycle.

Creating a company on install

Instead of companyId, pass companyName to have Envelope create a new Paperclip company and deploy into it in a single call. The created company's ID is returned in the response.

{
  "templateId": "tmpl_abc123",
  "companyName": "New Client Co",          // creates the company
  "paperclipBaseUrl": "https://paperclip.ing/api",
  "paperclipApiKey": "pc_admin_key_...",
  "variables": { "companyName": "New Client Co" }
}

Useful for provisioning pipelines that spin up a new Paperclip company per customer and immediately bootstrap it with an AI team.

Response

{
  "installId":  "inst_xyz",
  "platform":   "relevance_ai",
  "deployedAgents": [
    { "key": "sdr",      "externalId": "agent_abc", "name": "SDR Agent" },
    { "key": "ae-lead",  "externalId": "agent_def", "name": "AE Lead"   }
  ],
  "platformData": {
    "dashboardUrl": "https://app.relevanceai.com/agents/f1db6c/proj_...",
    "agentUrls": [
      { "name": "SDR Agent", "url": "https://app.relevanceai.com/agents/..." }
    ]
  },
  "warnings": [
    "analyst reports to sdr which reports to ae-lead — platform supports depth 1 only; hierarchy was flattened"
  ]
}

warnings is omitted when empty. It appears when the deploy succeeded but the platform required a silent adaptation — the most common case is hierarchy flattening on platforms that only support a single reporting level (agents can only report directly to the top-level manager, not to other agents). Treat warnings as informational: the team is live, but review them to confirm the deployed structure matches your intent.

Platform-specific parameters

Some request fields are only valid for specific platforms. Sending an unsupported field returns a 400 with a message explaining which platform requires it.

FieldEnvelopePaperclipRelevance AIBedrock
credentials
variables
secretspost-install
model
companyId / companyName
adapterType
webhookUrl
cwd

For http agents — managed vs. custom

Omit webhookUrl (recommended) and Envelope automatically configures its own managed runtime — no extra work required:

// webhookUrl omitted → Envelope uses its built-in runtime automatically

Or provide webhookUrl to route heartbeats to your own server instead — see the Runtime API for the wire format. Local adapter agents (claude_local, gemini_local, etc.) need no webhookUrl at all.

Billing — Envelope Managed vs. other platforms

Billing works differently depending on the platform:

envelopeUsage is tracked natively by Envelope (per run or per-k-tokens, depending on the template's pricing model) and collected at the end of each billing cycle. Attach stripeCustomerId + stripePaymentMethodId at install time so Envelope can charge automatically.
otherBilling may be handled by the external platform, pass-through to Envelope's billing system, or out-of-band — depending on the template configuration. Attaching Stripe fields enables native Envelope billing on top of any external platform costs.
{
  "templateId": "tmpl_abc123",
  "platform": "envelope",          // or "paperclip", etc.
  // Billing — card-on-file collected via the Card Setup Intent flow
  "deployerEmail":           "[email protected]",
  "stripeCustomerId":        "cus_...",
  "stripePaymentMethodId":   "pm_..."
}

These fields are optional. Omit them for free teams or when billing is handled out of band. Collect them via the Card Setup Intent flow first.

GET

Check Known Secrets

Returns which of a given list of secret keys Envelope already has stored in its credential vault for a specific company. Use this before re-installing to determine which secrets the deployer still needs to supply versus which Envelope can fill in automatically from a prior install.

GET /api/secrets/known
Auth Required
curl "https://openenvelope.org/api/secrets/known?companyId=org_789&keys=SLACK_TOKEN,ZENDESK_KEY" \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Query Parameters

companyIdThe Paperclip company ID to check secrets for. Required.
keysComma-separated list of secret key names to check (e.g. SLACK_TOKEN,ZENDESK_KEY). Required.

Response

{
  "known": ["SLACK_TOKEN"]
}
// ZENDESK_KEY was not in the list — deployer must provide it at install time
// SLACK_TOKEN was found in the vault — will be filled in automatically

How the credential vault works

When a deployer supplies secrets at install time, Envelope pushes them to Paperclip's secrets store and saves an encrypted copy in its own vault (keyed by companyId + secretKey). On re-install, Envelope checks the vault and pre-fills any secrets it already knows — the deployer only needs to enter secrets that are genuinely new or have changed.

POST

Trigger a Run

Trigger an Envelope team run from any external caller — Zapier, HubSpot, a webhook, or any HTTP client. Runs the full team against the provided task and returns the result. This is the core endpoint for all marketplace and automation integrations.

POST /api/installs/:installId/run
Auth Required
curl -X POST https://openenvelope.org/api/installs/ins_abc123/run \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task": "Summarise the open Zendesk tickets from the last 24 hours and draft a handoff note."
  }'

Request body

taskrequiredThe task description passed to the team as its input. Plain text. Can be a question, an instruction, or a structured prompt — whatever the team was built to handle.

Response

{
  "runId": "a3f8c1d2-...",
  "status": "completed",
  "response": "Here is the handoff note for the last 24 hours...",
  "agent": "supervisor",
  "inputTokens": 1842,
  "outputTokens": 412
}

How it works

The endpoint runs the full team. The supervisor agent receives the task and can delegate to sub-agents using its built-in delegate tools — one per agent in the team. Each sub-agent runs its own loop with its own prompt, model, and access policy. The supervisor synthesises the final response once all delegated work is complete. Secrets and variables are loaded from the install and substituted into every agent's prompt automatically. The run is recorded in observability.

Runs are synchronous — the connection stays open until the team finishes. For long-running or complex teams, consider triggering via a background job and polling the runs API for the result.

One endpoint, every integration

Every marketplace wrapper calls this same endpoint — Zapier actions, HubSpot workflow actions, Slack commands, Make modules, Power Automate connector actions. The platform-specific wrapper is just packaging around POST /api/installs/:installId/run.

POST

Invoke Agent

Bedrock only

Sends a message to your deployed Bedrock supervisor agent and returns the response. Envelope proxies the call using the AWS credentials stored at install time — no Bedrock SDK required in your application. Token counts are captured from the response stream and automatically recorded as an observability run.

POST /api/installs/:installId/invoke
Auth Required
curl -X POST "https://openenvelope.org/api/installs/{installId}/invoke" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "inputText": "Summarise the support queue from the last 24 hours",
    "sessionId": "session-abc-123"
  }'

Request body

inputText

required

The message to send to the agent. Max 100,000 characters.
sessionId

optional

Pass the same session ID on subsequent calls to continue a multi-turn conversation. Omit to start a fresh session. Bedrock generates a session ID if you don't provide one — check the response to retrieve it.

Response

{
  "ok": true,
  "runId": "f7a3c2d1-...",        // Envelope run record ID
  "sessionId": "session-abc-123", // Echo or Bedrock-assigned session
  "agentName": "Support Supervisor",
  "text": "Here's a summary of the last 24 hours...",
  "inputTokens": 1847,
  "outputTokens": 312
}

Data flow note

Because Envelope proxies the call, your input text and the agent's response transit Envelope's servers. Envelope does not persist the content — only the run count, token totals, and success/failure status are recorded. See the security guide for the full Bedrock data flow.

Your AWS CloudTrail will show InvokeAgent calls originating from Envelope's infrastructure IP — this is expected.

POST

Self-report a Run

Bedrock

If you invoke Bedrock directly from your own code (rather than through the Envelope proxy), use this endpoint to report the run back for observability and billing. This is the zero-proxy option — your LLM traffic stays entirely within AWS, and only the token count and status reach Envelope.

POST /api/installs/:installId/runs
Auth Required
curl -X POST "https://openenvelope.org/api/installs/{installId}/runs" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agentKey": "supervisor",
    "status": "completed",
    "inputTokens": 1847,
    "outputTokens": 312,
    "runId": "bedrock-request-id"
  }'

Request body

agentKey

required

The agent key from your team definition that was invoked (e.g. supervisor).
status

required

"completed" or "failed"
inputTokens

optional

Input token count from the Bedrock response. Required for per_k_tokens billing.
outputTokens

optional

Output token count from the Bedrock response.
runId

optional

The Bedrock request ID or your own identifier. Stored for deduplication and cross-referencing with CloudTrail.
errorMessage

optional

Error message if status is "failed".

Response

{ "ok": true, "runId": "f7a3c2d1-..." }
DELETE

Delete Install

Cancels a deployment and tears down all platform-side resources. For Bedrock installs, Envelope deletes every provisioned agent and its aliases from your AWS account, then removes the stored credentials from the Envelope vault. For other platforms the install is marked cancelled without touching platform resources (use each platform's own console to clean up).

Irreversible for Bedrock

Bedrock agents and aliases deleted by this endpoint cannot be recovered. Deleted agents stop incurring AWS charges immediately. If you need to redeploy later, create a new install.

DELETE /api/installs/:installId
Auth Required
curl -X DELETE "https://openenvelope.org/api/installs/{installId}" \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Response

// Bedrock install with 3 agents cleaned up:
{
  "ok": true,
  "installId": "uuid",
  "platform": "bedrock",
  "deletedAgents": 3
}

// Bedrock with a partial failure (one agent already gone):
{
  "ok": true,
  "installId": "uuid",
  "platform": "bedrock",
  "deletedAgents": 2,
  "warnings": ["Failed to delete alias for agent \"SDR\": ..."]
}

// Non-Bedrock install:
{
  "ok": true,
  "installId": "uuid",
  "platform": "relevance_ai",
  "deletedAgents": 0
}

IAM permissions required for Bedrock teardown

The stored caller credentials must have bedrock:DeleteAgent and bedrock:DeleteAgentAlias in addition to the deploy-time permissions. If the credentials lack these permissions, the endpoint returns a 502 with the AWS error detail.

Runtime API

For teams using the http adapter in managed mode, Envelope includes a built-in webhook handler. It receives Paperclip heartbeats, fetches the agent config, calls the LLM configured for that agent, and responds — no server required on your end.

Most teams use local adapters (claude_local, gemini_local, etc.) and don't need this section. The Runtime API documents Envelope's managed webhook endpoint and a test utility for verifying the LLM integration.

Managed by Envelope

Envelope auto-configures its own runtime URL at install time. Paperclip calls it; you don't set anything. Envelope runs the full agentic loop on your behalf.

Custom webhook

The deployer supplies their own URL. Paperclip calls that URL on every trigger. Your server receives the heartbeat payload below and must respond with the wire format shown.

POST

Managed Webhook Handler

Used automatically for http agents installed in managed mode. Envelope configures this URL on the agent at install time — you never set it manually. Paperclip calls it on every trigger; Envelope runs a full agentic loop and responds.

POST /api/runtime/webhook — auto-configured by Envelope
# Envelope sets the agent's Runtime URL to this automatically at install time:
https://your-envelope-domain/api/runtime/webhook?paperclipApiKey=pc_xxx

# Paperclip then POSTs this payload on each trigger:
{
  "runId": "run_abc",
  "agentId": "agent_xyz",
  "companyId": "org_123",
  "context": { "taskId": "...", "wakeReason": "..." }
}

What Envelope's managed runtime does

Runs a full agentic loop powered by gpt-4o-mini, up to 10 iterations per run. Supports:

  • External API calls via a built-in http_request tool (Slack, Zendesk, GitHub, email, etc.)
  • Deployer secrets injected as ${SECRET_NAME} in any URL or header field
  • Multi-step agentic loops — tool call → result → next LLM call, up to 10 iterations

Cannot access the local file system. For that, use a *_local adapter or bring a custom webhook.

POST

Custom Webhook — Wire Format

When the deployer supplies their own webhook URL, Paperclip calls your server directly using the same heartbeat payload. Your endpoint must receive it, run whatever inference or logic you want, and return a response in the format below.

POST <your-webhook-url> — payload Paperclip sends to your server
{
  "runId": "run_abc",
  "agentId": "agent_xyz",
  "companyId": "org_123",
  "context": { "taskId": "...", "wakeReason": "..." }
}

Response your server must return

{
  "runId": "run_abc",
  "agentId": "agent_xyz",
  "companyId": "org_123",
  "agentName": "Head of Sales",  // optional but useful for logs
  "response": "..."              // the agent's text output
}

Your server is fully responsible for fetching the agent config, calling the LLM, running any agentic loop, and returning the result. Use any model or framework you like — Envelope only sees the final response.

POST

Test Endpoint

Test the LLM integration directly without a live Paperclip instance. Provide a system prompt and task — returns the OpenAI response immediately.

POST /api/runtime/webhook/test
curl -X POST https://your-envelope-domain/api/runtime/webhook/test \
  -H "Content-Type: application/json" \
  -d '{
    "systemPrompt": "You are the head of sales at Acme Corp.",
    "task": "Draft a follow-up email for a prospect who went quiet."
  }'

Response

{
  "response": "Subject: Checking in..."
}

Observability API

Envelope automatically collects run data from deployed teams. Each platform has its own reporting mechanism — the ingest endpoint is the common receiver. You never call this directly; you query your data via GET /api/runs.

POST

Ingest Run Report

Called by platform integrations on each completed run. Authenticated via the install token provisioned at deploy time — not your app API key.

POST /api/runs/ingest
Install Token
POST /api/runs/ingest
Authorization: Bearer env_install_xxxxxxxx

{
  "platformAgentId": "agent_abc",  // platform's native agent ID
  "platform": "paperclip",         // "paperclip" | "crewai" | "relevance" | ...
  "runId": "run_xyz",
  "status": "completed",           // "completed" | "failed"
  "inputTokens": 1840,
  "outputTokens": 920,
  "errorMessage": null
}

How it works per platform

Paperclip — At install time, Envelope provisions the @envelope/paperclip-plugin with ENVELOPE_URL and ENVELOPE_API_KEY set automatically. The plugin fires on every task.completed event and posts to this endpoint with platformAgentId set to the Paperclip agent's native ID and platform: "paperclip".

Amazon Bedrock — Two options. (1) Use the proxy invoke endpoint — Envelope calls Bedrock on your behalf, extracts token counts from the response stream, and records the run automatically. No action required from the deployer. (2) Invoke Bedrock directly in your own code, then self-report via POST /api/installs/:installId/runs — your LLM traffic stays entirely within AWS.

Relevance AI — No push mechanism; Envelope polls the conversation list every 5 minutes. Token data is not available from the Relevance AI API, so only run counts are reported — per_k_tokens billing is not supported for Relevance AI installs.

CrewAI Enterprise (upcoming) — Envelope registers a crewWebhookUrl at deploy time. CrewAI fires crew_kickoff_completed with exact token counts; the receiver posts to ingest using platformAgentId + platform: "crewai".

GET

Query Run Stats

Returns summary stats and a per-agent breakdown of all recorded runs for a deployed team. This powers the Observability dashboard — you can also query it directly.

GET /api/runs
Auth Required
curl "https://openenvelope.org/api/runs?templateSlug=startup-ops-team" \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Query Parameters

templateIdrequired* — Template UUID. Use either this or templateSlug.
templateSlugrequired* — Human-readable slug (e.g. startup-ops-team). Resolved to ID server-side.
limitMax recent runs to return in the runs array (default: 100, max: 500). Does not affect summary, byAgent, or byPlatform — those are always computed across all runs.

* At least one of templateId or templateSlug is required. Returns 400 if neither is provided.

Response

{
  "summary": {
    "totalRuns": 592,
    "failedRuns": 35,
    "successRate": 94,         // number (0-100), or null if totalRuns is 0
    "totalInputTokens": 770491,
    "totalOutputTokens": 339135,
    "totalToolCalls": 0,
    "failedToolCalls": 0
  },
  "byPlatform": [
    {
      "platform": "paperclip",
      "totalRuns": 521,
      "failedRuns": 30,
      "inputTokens": 770491,
      "outputTokens": 339135,
      "hasTokens": true
    },
    {
      "platform": "relevance_ai",
      "totalRuns": 71,
      "failedRuns": 5,
      "inputTokens": 0,
      "outputTokens": 0,
      "hasTokens": false
    }
  ],
  "byAgent": [
    {
      "agentKey": "ops-analyst",
      "runs": 128,
      "failed": 7,
      "inputTokens": 49403,
      "outputTokens": 24697
    },
    ...
  ],
  "runs": [
    {
      "id": "019250ab-...",
      "installId": "019250ab-...",
      "platform": "paperclip",
      "agentKey": "chief-of-staff",
      "status": "completed",
      "inputTokens": 1840,
      "outputTokens": 920,
      "errorMessage": null,
      "createdAt": "2025-04-05T09:12:00.000Z"
    },
    ...
  ]
}

Billing API

Envelope tracks every agent run and computes cost based on the pricing model you set on your team. Builders earn their share; Envelope retains a fixed 10% platform fee. Payouts are handled via Stripe Connect.

Pricing guidance for builders

A good starting point is to charge 3–5× your underlying model API cost. This covers infrastructure margin while staying cheap enough that deployers don't think twice. Envelope takes its cut on top — you keep the rest.

Light teams

1–2 agents, short tasks

$0.01 – $0.05 / run

Mid-complexity

3–5 agents, tool use

$0.05 – $0.25 / run

Heavy teams

Long-context, many agents

$0.25 – $1.00+ / run

For variable-length tasks, consider per-1K-token pricing at $0.002–$0.01. At 400 runs/month, a $0.05/run team earns ~$18/month after Envelope's cut — real passive income at scale.

Read the full pricing guide →
GET

Query Billing Estimate

Returns a cost breakdown for a team over a rolling period — what deployers were charged, what builders earned, and Envelope's cut. Useful for invoicing dashboards and revenue reporting.

GET /api/billing
Auth Required
curl "https://openenvelope.org/api/billing?templateSlug=startup-ops-team&periodDays=30" \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Query Parameters

templateIdTemplate UUID. Use either this or templateSlug.
templateSlugHuman-readable slug (e.g. startup-ops-team).
installIdOptional. Scope the estimate to a single deployer's install.
periodDaysRolling window in days (default: 30, max: 365).

Response

{
  "templateId": "01925000-0000-7000-8000-000000000001",
  "templateSlug": "startup-ops-team",
  "pricing": {
    "model": "per_run",           // "free" | "per_run" | "per_k_tokens"
    "priceUsdCents": 5,           // cents per run (or per 1K tokens)
    "envelopeFeePercent": 10,
    "priceDisplay": "$0.0500/run"
  },
  "period": {
    "days": 30,
    "start": "2026-03-06T00:00:00.000Z",
    "end": "2026-04-05T00:00:00.000Z"
  },
  "usage": {
    "totalRuns": 408,             // all runs in period (including failed)
    "completedRuns": 388,         // only these are billed
    "totalTokens": 762702,        // all tokens in period
    "billedTokens": 724681        // tokens from completed runs only
  },
  "billing": {
    "totalAmountCents": 1940,     // deployer pays this (388 × $0.05)
    "builderAmountCents": 1746,   // builder receives this (90%)
    "envelopeFeeCents": 194,      // Envelope retains this (10%)
    "projectedMonthlyAmountCents": 1940,
    "totalAmountDisplay": "$19.40",
    "projectedMonthlyDisplay": "$19.40"
  },
  "byInstall": [
    { "installId": "inst_abc", "runs": 208, "tokens": 390000, "amountCents": 1040 },
    ...
  ]
}
PATCH

Update Pricing

Set or update the pricing model for one of your templates. You must own the template (i.e. it was created with your API key). Works on both draft and published templates.

PATCH /api/billing/pricing
Auth Required
curl -X PATCH https://openenvelope.org/api/billing/pricing \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "01925000-0000-7000-8000-000000000001",
    "pricingModel": "per_run",   // "free" | "per_run" | "per_k_tokens"
    "priceUsdCents": 5           // cents per run (or per 1K tokens)
  }'

Pricing Models

freeNo charge. Deployers pay nothing. Set priceUsdCents to null.
per_runCharged per agent run. E.g. priceUsdCents: 5 = $0.05 per run.
per_k_tokensCharged per 1,000 tokens consumed. E.g. priceUsdCents: 2 = $0.02 per 1K tokens. Platform-limited — supported on Paperclip only. Relevance AI deployments do not report token counts; runs will not be billed under this model.

Revenue split

Deployers are charged totalAmountCents per period. Envelope retains a fixed 10% platform fee and routes the remaining 90% to your Stripe Connect account automatically.

Response

{
  "id": "01925000-0000-7000-8000-000000000001",
  "pricingModel": "per_run",
  "priceUsdCents": 5,
  "envelopeFeePercent": 10
}

Payments & Payouts

Envelope uses Stripe for all payment flows. Deployers attach a card-on-file at deploy time; builders connect a Stripe Express account to receive their 90% share automatically.

Authentication — two patterns

Most /api/stripe/* endpoints require a valid session cookie (envelope_session) and are designed to be called from the Envelope frontend on behalf of a logged-in user. Two endpoints use an App API Key instead: POST /api/stripe/setup-intent (called during the deploy wizard before a session exists) and POST /api/stripe/charge-cycle (admin/scheduler trigger).

POST

Card Setup Intent

Creates a Stripe SetupIntent and Stripe Customer for a deployer. The returned clientSecret is used to render the Stripe PaymentElement and collect card details. Once confirmed, the resulting paymentMethodId is attached to the install record.

POST /api/stripe/setup-intent
API Key Required
curl -X POST https://openenvelope.org/api/stripe/setup-intent \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]" }'

Response

{
  "clientSecret": "seti_..._secret_...",
  "customerId":   "cus_..."
}
POST

Builder Payout Onboarding

Creates (or resumes) a Stripe Express account for the authenticated builder and returns a one-time onboarding URL. Redirect the user to this URL to complete identity verification. On return, Envelope polls Connect status to update the builder's payout state.

POST /api/stripe/connect/onboard
Session Required
curl -X POST https://openenvelope.org/api/stripe/connect/onboard \
  -H "Content-Type: application/json" \
  --cookie "envelope_session=<token>" \
  -d '{ "returnUrl": "https://yourapp.com/account?stripe_connect=return" }'

Response

{ "url": "https://connect.stripe.com/setup/e/..." }

Platform requirement

Your Stripe platform account must have Connect enabled. If not, this endpoint returns a 422 with code: "connect_enrollment_required" and a direct link to the Stripe Connect dashboard.

GET

Connect Status

Returns the current Stripe Connect status for the authenticated builder. Use this to determine whether a builder can receive payouts.

GET /api/stripe/connect/status
Session Required
curl https://openenvelope.org/api/stripe/connect/status \
  --cookie "envelope_session=<token>"

Response

// Not connected
{ "status": "not_connected" }

// Onboarding incomplete
{ "status": "pending_onboarding" }

// Active — payouts enabled
{
  "status":       "active",
  "dashboardUrl": "https://connect.stripe.com/express/..."
}
not_connectedBuilder has not started Connect onboarding. Call the onboard endpoint to begin.
pending_onboardingAccount created but identity verification is incomplete. Re-call onboard to resume.
activeAccount is fully verified. Payouts will be triggered automatically each billing cycle.
restrictedAccount has restrictions. Builder must complete additional verification in their Stripe Express dashboard.
POST

Charge Billing Cycle

Charges a deployer's card-on-file for a specific amount. The billing scheduler calls this automatically at the start of each month for all active paid installs — but you can also call it manually for custom billing intervals or ad-hoc charges. Requires an App API Key (not a session cookie).

Automated by default

Envelope's billing scheduler runs on the 1st of each month (UTC) and handles this automatically for all installs with a saved payment method. Use this endpoint only for custom or ad-hoc billing. Override the schedule with the BILLING_CRON_SCHEDULE environment variable (standard cron syntax).

POST /api/stripe/charge-cycle
API Key Required
curl -X POST https://openenvelope.org/api/stripe/charge-cycle \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "installId":   "inst_xyz",
    "amountCents": 500
  }'
installId
required
stringThe install ID to charge.
amountCents
required
integerAmount in USD cents to charge (e.g. 500 = $5.00). Must be ≥ 50 (Stripe minimum).
periodStart
optional
stringISO-8601 timestamp. Start of the billing window recorded on the invoice. Defaults to the start of the previous calendar month.
periodEnd
optional
stringISO-8601 timestamp. End of the billing window recorded on the invoice. Defaults to the end of the previous calendar month.

Response

// Charge succeeded
{
  "invoiceId":       "01925000-...",
  "paymentIntentId": "pi_...",
  "status":          "succeeded",
  "amountCents":     500
}

// Period was already billed (idempotent)
{
  "status":    "already_paid",
  "invoiceId": "01925000-..."
}
GET

List Installs

Returns all installs associated with your API key, ordered newest-first. Use this to query the state of your deployed teams programmatically.

GET /api/installs
Auth Required
curl https://openenvelope.org/api/installs \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Response

{
  "installs": [
    {
      "id": "inst_abc123",
      "templateId": "tmpl_xyz",
      "templateName": "Outbound Sales Team",
      "templateSlug": "sales-team-v1",
      "templateVersion": 3,
      "platform": "paperclip",
      "companyId": "org_789",
      "paperclipBaseUrl": "https://paperclip.ing/api",
      "status": "completed",
      "createdAt": "2026-04-01T12:00:00.000Z"
    },
    ...
  ]
}
GET

My Managed Installs

Returns all Envelope Managed installs belonging to the authenticated session user — either directly deployed by them or accessible via their org membership. Includes config completeness signals so the operator UI can immediately surface which installs still need secrets or variables configured.

This endpoint uses session auth only (not API key) — it powers the logged-in Pipelines dashboard. To resolve which install an API key is scoped to, use GET /installs/whoami instead.
GET /api/installs/mine
Session Required
curl https://openenvelope.org/api/installs/mine \
  --cookie "envelope_session=<token>"
[
  {
    "id": "inst_abc123",
    "name": "Acme Corp",              // org name, or template name if no org
    "pipelineName": "Acme Corp",      // pipeline display name from definition
    "orgSlug": "acme-corp",           // null if not linked to an org
    "status": "completed",
    "platform": "envelope",
    "lastRunAt": "2026-05-03T09:00:04.000Z",   // null if never run
    "nextRunAt": "2026-05-04T09:00:00.000Z",   // null if no schedule
    "webhookToken": "wh_abc123xyz",            // null if not enabled
    "requiredVariables": ["COMPANY_NAME", "TIMEZONE"],
    "currentVariables":  { "COMPANY_NAME": "Acme Corp", "TIMEZONE": "UTC" },
    "missingSecrets":   ["SLACK_BOT_TOKEN"],   // required secrets not yet configured
    "missingVariables": []                      // required variables not yet configured
  },
  ...
]

missingSecrets / missingVariables

These arrays are computed at query time by comparing the template's requiredSecrets and requiredVariables against what's currently stored. Non-empty arrays indicate the install is not yet fully configured and the pipeline will likely fail if triggered.

lastRunAt / nextRunAt

lastRunAt is the timestamp of the most recent pipeline execution for this install. nextRunAt is derived from the configured cron schedule and timezone — it is null when no schedule is set or the schedule is disabled.

GET

Resolve Install (whoami)

Resolves which install ID the current API key is scoped to. The Pipelines frontend calls this once after key entry to establish its pipeline context. Returns 400 if the key has no install scope — either not yet linked or not an Envelope Managed key.

When is a key automatically scoped?

When you create an Envelope Managed install (platform: "envelope"), the API key used to make that call is automatically linked to the new installId. Subsequent calls to this endpoint with that same key return immediately without any additional setup.

Keys can also be scoped via the APP_API_KEYS environment variable using the installId:key format — useful for self-hosted or development environments.

GET /api/installs/whoami
API Key Required
curl https://openenvelope.org/api/installs/whoami \
  -H "Authorization: Bearer $ENVELOPE_KEY"
// Key is scoped — returns install ID
{ "installId": "inst_abc123" }

// Key has no install scope
// 400 Bad Request
{
  "error": "This API key is not linked to a pipeline install.",
  "hint": "For APP_API_KEYS, use the format 'installId:key'. For user API keys, set install_id via the key management API."
}
PATCH

Update Variables

Merges the supplied key-value pairs into an install's variable set. Existing keys are overwritten; keys not included are left unchanged. Accepts both a flat object and a { variables: { ... } } wrapper.

PATCH /api/installs/:installId/variables
Auth Required
curl -X PATCH "https://openenvelope.org/api/installs/{installId}/variables" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "companyName": "Acme Corp v2",
    "supportEmail": "[email protected]"
  }'

Response

{
  "ok": true,
  "installId": "inst_abc123",
  "updatedKeys": ["companyName", "supportEmail"]
}
PATCH

Rotate Secrets

Merges new secret values into an install's secret store without requiring a full redeploy. Useful for key rotation. Accepts both a flat object and a { secrets: { ... } } wrapper.

Encrypted at rest

All secret values are encrypted before storage. The plaintext value you send is never logged or persisted anywhere other than the encrypted vault record.

PATCH /api/installs/:installId/secrets
Auth Required
curl -X PATCH "https://openenvelope.org/api/installs/{installId}/secrets" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "SLACK_BOT_TOKEN": "xoxb-new-token-...",
    "ZENDESK_API_KEY": "zdapi_new_..."
  }'

Response

{
  "ok": true,
  "installId": "inst_abc123",
  "updatedKeys": ["SLACK_BOT_TOKEN", "ZENDESK_API_KEY"]
}

Pipelines API

Programmatic access to the Pipelines operator console — monitor run health, cancel active runs, resume after human gates, manage schedules, review gate queues, and read the activity feed. All endpoints accept both a session cookie and an API key (Authorization: Bearer).

GET

Run Health Log

Returns a day-by-day breakdown of completed and failed pipeline steps for the last N days (default 7, max 30). Use this to monitor pipeline health over time, build dashboards, or trigger alerts when error counts spike.

GET /api/installs/:id/run-log
Auth Required
curl "https://openenvelope.org/api/installs/{id}/run-log?days=7" \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Query parameters

daysNumber of days to include (1–30). Defaults to 7.

Response

{
  "days": [
    { "date": "2026-04-27", "ok": 0, "error": 0 },  // no steps ran
    { "date": "2026-04-28", "ok": 14, "error": 1 },
    { "date": "2026-04-29", "ok": 18, "error": 0 },
    { "date": "2026-04-30", "ok": 9,  "error": 3 },
    { "date": "2026-05-01", "ok": 21, "error": 0 },
    { "date": "2026-05-02", "ok": 17, "error": 0 },
    { "date": "2026-05-03", "ok": 22, "error": 0 }
  ]
}
Counts reflect individual step events (step_completed / step_failed), not full pipeline runs. A single pipeline execution with three agents increments ok by three when all succeed. Days are returned oldest-first in the caller's requested range.
GET

Agent Run History

Returns recent individual agent executions for an install, newest-first. Each record represents one agent invocation — use this to audit token usage, track per-agent failure rates, and correlate Envelope run IDs with your own logs.

GET /api/installs/:id/agent-runs
Auth Required
curl "https://openenvelope.org/api/installs/{id}/agent-runs?limit=20" \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Query parameters

limitNumber of records to return (1–200). Defaults to 50.

Response

{
  "runs": [
    {
      "id": "ar_abc123",
      "agentKey": "supervisor",
      "runId": "a3f8c1d2-...",          // parent pipeline run ID
      "status": "completed",            // "completed" | "failed"
      "inputTokens": 1842,
      "outputTokens": 412,
      "errorMessage": null,             // set on failure
      "createdAt": "2026-05-03T09:00:03.000Z"
    },
    {
      "id": "ar_def456",
      "agentKey": "writer",
      "runId": "a3f8c1d2-...",
      "status": "failed",
      "inputTokens": 924,
      "outputTokens": 0,
      "errorMessage": "Rate limit exceeded from upstream",
      "createdAt": "2026-05-03T09:00:01.000Z"
    }
  ]
}
POST

Cancel Run

Cancels the currently active pipeline run for an install. The running agent's proxy token is revoked — the agent errors out on its next outbound request, stopping execution cleanly. A step_cancelled event is emitted in the activity feed. Returns 404 if no run is currently active.

POST /api/installs/:id/cancel-run
Auth Required
curl -X POST "https://openenvelope.org/api/installs/{id}/cancel-run" \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Response

{
  "ok": true,
  "runId": "a3f8c1d2-...",
  "cancelledStep": "writer"   // key of the step that was active when cancelled
}
POST

Resume after Gate

Resumes a pipeline that is paused at a human gate checkpoint. The server recovers the gated step's output and continues execution from the next agent in the sequence. Returns 409 if no pending gate is found for this install.

POST /api/installs/:id/continue
Auth Required
curl -X POST "https://openenvelope.org/api/installs/{id}/continue" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "bypassGates": false }'

Request body

bypassGates

optional · boolean

If true, all subsequent gate checkpoints in this run are skipped automatically. Defaults to false.

Response

{ "ok": true }

// No pending gate:
// 409 { "error": "No pending gate found for this install" }

// No agents remaining after the gate:
// 409 { "error": "No agents remaining after gate" }

Human gate flow

When a pipeline step reaches a gate checkpoint, execution pauses and a gate_triggered event is emitted. The operator reviews items in the gate queue (see Human gate queue) and calls this endpoint to resume. The pipeline picks up from where it left off, passing the gated step's output into the next agent.

GET

Get Schedule

Returns the cron schedule configured for an install, along with the last and next run timestamps. Also returns the list of available IANA timezone strings for schedule setup UIs.

GET /api/installs/:id/schedule
Auth Required
curl https://openenvelope.org/api/installs/{id}/schedule \
  -H "Authorization: Bearer $ENVELOPE_KEY"
{
  "schedule": {
    "cronExpression": "0 9 * * 1-5",
    "label": "Weekdays at 9am",
    "enabled": true,
    "onBacklog": "skip",        // "skip" | "queue" — what to do if prior run is still active
    "timezone": "America/New_York",
    "bypassGates": false,       // if true, runs skip all human gate checkpoints
    "lastRunAt": "2026-04-22T09:00:00.000Z",
    "nextRunAt": "2026-04-23T09:00:00.000Z"
  },
  "timezones": ["America/New_York", "Europe/London", "Asia/Tokyo", ...]
}

// No schedule configured:
{ "schedule": null, "timezones": [...] }
PATCH

Update Schedule

Creates or replaces the cron schedule for an install. Pass cronExpression: null to delete the schedule entirely.

PATCH /api/installs/:id/schedule
Auth Required
# Set a schedule
curl -X PATCH "https://openenvelope.org/api/installs/{id}/schedule" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cronExpression": "0 9 * * 1-5",
    "label": "Weekdays at 9am",
    "enabled": true,
    "onBacklog": "skip",
    "timezone": "America/New_York",
    "bypassGates": false
  }'

# Delete the schedule
curl -X PATCH "https://openenvelope.org/api/installs/{id}/schedule" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "cronExpression": null }'
cronExpression

required

Standard 5-field cron expression (minute hour day month weekday). Pass null to delete the schedule.
label

optional

Human-readable name shown in the Pipelines UI (e.g. "Weekdays at 9am").
enabled

optional

Whether the schedule is active. Defaults to true. Can also be toggled via the toggle endpoint.
onBacklog

optional

"skip" (default) — skip the tick if a prior run is still active. "queue" — queue the tick to run once the current run finishes.
timezone

optional

IANA timezone string. Defaults to "UTC". Use the timezones array from GET /schedule for valid values.
bypassGates

optional

If true, scheduled runs skip all human gate checkpoints. Useful for fully-automated overnight runs. Defaults to false.
{
  "ok": true,
  "schedule": {
    "cronExpression": "0 9 * * 1-5",
    "label": "Weekdays at 9am",
    "enabled": true,
    "onBacklog": "skip",
    "timezone": "America/New_York",
    "bypassGates": false,
    "lastRunAt": null,
    "nextRunAt": "2026-04-23T09:00:00.000Z"
  }
}
PATCH

Toggle Schedule

Pause or resume a schedule without changing any other settings. Equivalent to toggling the switch in the Pipelines UI.

PATCH /api/installs/:id/schedule/toggle
Auth Required
curl -X PATCH "https://openenvelope.org/api/installs/{id}/schedule/toggle" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
{ "ok": true, "enabled": false }
GET

Human gate queue

Returns the pending-review records for a named human gate checkpoint. The name path parameter must match a gate declared in the team definition. Built-in gate names are target-review and draft-approval. Unknown gate names return an empty queue gracefully.

What is a human gate?

Human gates are declared checkpoints in a team definition where agent output pauses for human review before the pipeline continues. They are defined in the template's gates field. See the Human gates guide for the full spec.

GET /api/installs/:id/gates/:name/queue
Auth Required
curl https://openenvelope.org/api/installs/{id}/gates/draft-approval/queue \
  -H "Authorization: Bearer $ENVELOPE_KEY"
// gate: draft-approval
{
  "records": [
    {
      "id": 42,
      "status": "pending_approval",
      "subject": "Quick question about your open source work",
      "body": "Hi Sarah, I came across your work on...",
      "recipientEmail": "[email protected]",
      "targetHandle": "sarahdev"
    }
  ],
  "total": 1
}

// gate: target-review
{
  "records": [
    {
      "id": 7,
      "githubHandle": "sarahdev",
      "email": "[email protected]",
      "priorityScore": 92,
      "status": "pending",
      "categoryFit": "strong match — active OSS contributor",
      "bio": "Senior engineer at Stripe, focused on developer tooling..."
    }
  ],
  "total": 1
}
GET

List Drafts

Returns all outreach drafts for an install, joined with their target contact. Ordered newest-first. Drafts are created by the agent pipeline and queued for human review before sending.

GET /api/installs/:id/drafts
Auth Required
curl https://openenvelope.org/api/installs/{id}/drafts \
  -H "Authorization: Bearer $ENVELOPE_KEY"
{
  "drafts": [
    {
      "draft": {
        "id": 42,
        "installId": "inst_abc",
        "targetId": 7,
        "status": "pending_approval",   // pending_approval | approved | rejected | sent | archived
        "subject": "Quick question about your open source work",
        "body": "Hi Sarah, ...",
        "rejectionNote": null,
        "editedAt": null,
        "createdAt": "2026-04-22T10:00:00.000Z"
      },
      "target": {
        "id": 7,
        "installId": "inst_abc",
        "githubHandle": "sarahdev",
        "email": "[email protected]",
        "priorityScore": 92,
        "status": "approved"
      }
    }
  ]
}
POST

Approve Draft

Marks a draft as approved. The pipeline's Sender step picks up approved drafts on its next run and dispatches them. Triggers an auto-advance check so the pipeline resumes immediately if all gate conditions are satisfied.

POST /api/installs/:id/drafts/:draftId/approve
Auth Required
curl -X POST "https://openenvelope.org/api/installs/{id}/drafts/42/approve" \
  -H "Authorization: Bearer $ENVELOPE_KEY"
{ "ok": true }

Optional — edit before approving

Use PATCH /installs/:id/drafts/:draftId to update subject and/or body before calling approve. editedAt is stamped automatically.

POST

Reject Draft

Marks a draft as rejected. Supply an optional note to give the Writer agent context for re-running — this note becomes part of the feedback loop and can meaningfully improve the next iteration.

POST /api/installs/:id/drafts/:draftId/reject
Auth Required
curl -X POST "https://openenvelope.org/api/installs/{id}/drafts/42/reject" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Too generic — reference their specific project next time" }'
{ "ok": true }
To undo an accidental approval (before sending), use POST /installs/:id/drafts/:draftId/recall — this moves the draft back to pending_approval. Returns 409 if the draft has already been sent.
POST

Bulk Draft Action

Apply an action to multiple drafts at once. Target drafts by explicit ID list or by status filter. Returns the count of records updated.

POST /api/installs/:id/drafts/bulk
Auth Required
# Approve specific drafts by ID
curl -X POST "https://openenvelope.org/api/installs/{id}/drafts/bulk" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "action": "approve", "ids": [42, 43, 44] }'

# Reject all pending_approval drafts
curl -X POST "https://openenvelope.org/api/installs/{id}/drafts/bulk" \
  -H "Authorization: Bearer $ENVELOPE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "action": "reject", "filter": { "status": "pending_approval" } }'
action

required

"approve" | "reject" | "requeue" | "archive". Requeue moves drafts back to pending_approval (clears rejection note). Archive hides them.
ids

optional

Array of integer draft IDs to target. Provide either ids or filter, not both.
filter.status

optional

Apply to all drafts matching this status (e.g. "pending_approval", "rejected"). Provide either ids or filter.
{ "ok": true, "count": 3 }
GET

Events Feed

Returns the last 100 pipeline events for an install, ordered newest-first. Events include gate approvals, rejections, step completions, and scheduling actions. Powers the Activity panel in the Pipelines console.

GET /api/installs/:id/events
Auth Required
curl https://openenvelope.org/api/installs/{id}/events \
  -H "Authorization: Bearer $ENVELOPE_KEY"
{
  "events": [
    {
      "id": "evt_abc",
      "eventType": "gate_approved",   // gate_approved | gate_rejected | step_complete | scheduler_run | ...
      "stepKey": "writer",
      "gateName": "draft-approval",
      "actorId": "usr_xyz",
      "actorEmail": "[email protected]",
      "meta": { "draftId": 42 },
      "createdAt": "2026-04-22T11:05:00.000Z"
    },
    ...
  ]
}
GET

Security Policies

Returns each agent in the install together with its declared accessPolicy. Use this to audit which agents are allowed to call which external hosts, and what the fallback behaviour is for unmatched requests.

GET /api/installs/:id/policies
Auth Required
curl https://openenvelope.org/api/installs/{id}/policies \
  -H "Authorization: Bearer $ENVELOPE_KEY"
{
  "agents": [
    {
      "key": "sdr",
      "name": "SDR Agent",
      "policy": {
        "defaultAction": "BLOCK",
        "rules": [
          { "hosts": ["api.sendgrid.com"], "action": "ALLOW" },
          { "hosts": ["*.internal.acme.com"], "action": "JUDGE" }
        ]
      }
    },
    {
      "key": "analyst",
      "name": "Analyst",
      "policy": null    // no policy declared — all requests pass through
    }
  ]
}
The accessPolicy schema is defined in the Access Policy Schema section. See the Security guide for enforcement details.
GET

Security Request Log

Returns the last 200 outbound HTTP requests made by agents in this install, with their policy decision and reasoning. Powers the Security dashboard in the Pipelines console. Use this to audit which requests were allowed, blocked, or sent to the judge.

GET /api/installs/:id/security/requests
Auth Required
curl https://openenvelope.org/api/installs/{id}/security/requests \
  -H "Authorization: Bearer $ENVELOPE_KEY"
{
  "requests": [
    {
      "id": "req_001",
      "agentKey": "sdr",
      "agentName": "SDR Agent",
      "runId": "run_xyz",
      "method": "POST",
      "url": "https://api.sendgrid.com/v3/mail/send",
      "hostname": "api.sendgrid.com",
      "path": "/v3/mail/send",
      "decision": "ALLOW",          // ALLOW | BLOCK | JUDGE | PASSTHROUGH
      "decisionSource": "rule",     // rule | default | judge
      "reason": "matched allow rule for api.sendgrid.com",
      "judgeReasoning": null,       // LLM explanation when decision is JUDGE
      "judgeConfidence": null,      // 0–1 confidence score from judge
      "statusCode": 202,
      "latencyMs": 341,
      "createdAt": "2026-04-22T11:05:00.000Z"
    },
    {
      "id": "req_002",
      "agentKey": "sdr",
      "agentName": "SDR Agent",
      "runId": "run_xyz",
      "method": "GET",
      "url": "https://unknown-api.io/endpoint",
      "hostname": "unknown-api.io",
      "path": "/endpoint",
      "decision": "BLOCK",
      "decisionSource": "default",
      "reason": "no matching rule — defaultAction is BLOCK",
      "judgeReasoning": null,
      "judgeConfidence": null,
      "statusCode": null,
      "latencyMs": 0,
      "createdAt": "2026-04-22T11:05:01.000Z"
    }
  ]
}
ALLOWRequest matched an ALLOW rule and was forwarded to the destination.
BLOCKRequest was blocked — either matched a BLOCK rule or hit the defaultAction.
JUDGERequest was sent to the LLM judge. The judge's decision is in judgeReasoning / judgeConfidence.
PASSTHROUGHAgent has no access policy — request was forwarded without any check.

Orgs API

Create and manage organisations, their members, API keys, and deployment governance. All org endpoints require a session cookie (envelope_session) — they are not accessible via API key. Use them from your own web dashboard or authenticated backend service.

POST

Create Org

Creates a new organisation. The authenticated user automatically becomes the owner. Envelope derives a URL-safe slug from the name — it must be unique across all orgs.

POST /api/orgs
Session Required
curl -X POST https://openenvelope.org/api/orgs \
  --cookie "envelope_session=<token>" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Corp" }'
{
  "org": {
    "id": "org_abc",
    "name": "Acme Corp",
    "slug": "acme-corp",
    "plan": "free",
    "createdAt": "2026-04-24T00:00:00.000Z"
  }
}
GET

List My Orgs

Returns all organisations the authenticated user belongs to, along with their role in each.

GET /api/orgs/mine
Session Required
curl https://openenvelope.org/api/orgs/mine \
  --cookie "envelope_session=<token>"
{
  "orgs": [
    {
      "role": "owner",
      "org": { "id": "org_abc", "name": "Acme Corp", "slug": "acme-corp", "plan": "pro", ... }
    },
    {
      "role": "member",
      "org": { "id": "org_def", "name": "Partner Inc", "slug": "partner-inc", "plan": "free", ... }
    }
  ]
}
GET

Get Org

Returns full details for a single org including member count, recent templates, and the caller's role. Requires membership.

GET /api/orgs/:slug
Session Required
curl https://openenvelope.org/api/orgs/acme-corp \
  --cookie "envelope_session=<token>"
{
  "org": {
    "id": "org_abc",
    "name": "Acme Corp",
    "slug": "acme-corp",
    "plan": "pro",
    "billingEmail": "[email protected]",
    "avatarUrl": null,
    "governanceEnabled": true,
    "currentPeriodEnd": "2026-05-01T00:00:00.000Z",
    "cancelAtPeriodEnd": false
  },
  "memberCount": 4,
  "recentTemplates": [ ... ],
  "myRole": "owner"             // "owner" | "admin" | "member"
}
PATCH

Update Org

Updates the org's name, billing email, or avatar URL. Requires owner or admin role. All fields are optional — only supplied fields are updated.

PATCH /api/orgs/:slug
Session Required (owner / admin)
curl -X PATCH https://openenvelope.org/api/orgs/acme-corp \
  --cookie "envelope_session=<token>" \
  -H "Content-Type: application/json" \
  -d '{ "billingEmail": "[email protected]" }'
{ "org": { ... } }  // Updated org object
DELETE

Delete Org

Permanently deletes the organisation. Templates owned by the org revert to personal ownership rather than being deleted. Requires owner role.

Irreversible

All members are removed, all invitations are cancelled, and all org-level API keys are revoked. Templates are not deleted — they revert to personal ownership.

DELETE /api/orgs/:slug
Session Required (owner)
curl -X DELETE https://openenvelope.org/api/orgs/acme-corp \
  --cookie "envelope_session=<token>"
{ "ok": true }

Members

List, update, and remove org members. Roles are owner, admin, and member. Owners cannot be removed via the remove-member endpoint — use the transfer endpoint first.

GET
/api/orgs/:slug/members

List all members with their user details and role.

Requires: all members

PATCH
/api/orgs/:slug/members/:userId/role

Change a member's role to admin or member. Cannot change the owner's role.

Requires: owner / admin

DELETE
/api/orgs/:slug/members/:userId

Remove a member, or self-remove as a member.

Requires: owner / admin (or self)

PATCH
/api/orgs/:slug/transfer

Transfer ownership to an existing admin. Body: { newOwnerId, confirmation (must equal org name) }.

Requires: owner

// GET /api/orgs/:slug/members
{
  "members": [
    {
      "memberId": "mem_abc",
      "role": "owner",
      "joinedAt": "2026-01-01T00:00:00.000Z",
      "userId": "usr_xyz",
      "name": "Alice",
      "handle": "alice",
      "email": "[email protected]",
      "avatarUrl": null
    }
  ]
}

Invitations

Send, list, and cancel pending org invitations. Invitations expire after 7 days. Sending to an already-pending email replaces the existing invite token.

POST
/api/orgs/:slug/invitations

Send an email invitation to join the org. Body: { email, role (admin|member) }. Sends an invitation email immediately.

Requires: owner / admin

GET
/api/orgs/:slug/invitations

List pending (not yet accepted, not expired) invitations.

Requires: owner / admin

DELETE
/api/orgs/:slug/invitations/:id

Cancel a pending invitation by its ID.

Requires: owner / admin

Org API Keys

Org-level API keys are tied to the organisation rather than any individual user. Use these for CI/CD and shared automation so access doesn't break when people leave. The raw key is only returned once at creation — store it securely.

GET
/api/orgs/:slug/keys

List all org API keys (id, label, createdAt only — key value is never returned after creation).

Requires: all members

POST
/api/orgs/:slug/keys

Create a new org API key. Body: { label? }. Returns the raw key — save it immediately.

Requires: owner / admin

DELETE
/api/orgs/:slug/keys/:id

Revoke an org API key. Immediate effect — any requests using this key will fail with 401.

Requires: owner / admin

// POST /api/orgs/:slug/keys response — save this key, it's only shown once
{
  "id": "key_abc",
  "key": "env_live_...",
  "label": "CI deploy key",
  "createdAt": "2026-04-24T00:00:00.000Z"
}

Deployment Governance

When governance is enabled on an org, all new deployments require approval from an owner or admin before going live. This is a Pro-plan feature. Approval requests notify all org admins by email.

Pro plan required

These endpoints return 402 if the org is not on a Pro plan. Upgrade via Pricing or the org billing settings.

PATCH
/api/orgs/:slug/settings/governance

Enable or disable deployment governance. Body: { enabled: boolean }.

Requires: owner / admin

GET
/api/orgs/:slug/approvals

List approval requests. Filter by status: ?status=pending|approved|rejected.

Requires: all members

POST
/api/orgs/:slug/approvals

Submit a deployment for review. Body: { installId, templateId, templateName }. Notifies all org admins.

Requires: all members

PATCH
/api/orgs/:slug/approvals/:id

Approve or reject a request. Body: { status: 'approved'|'rejected', note? }. Notifies the requestor.

Requires: owner / admin

// GET /api/orgs/:slug/approvals?status=pending
{
  "approvals": [
    {
      "id": "appr_uuid",
      "orgId": "org_abc",
      "installId": "inst_xyz",
      "templateId": "tmpl_abc",
      "templateName": "Outbound Sales Team",
      "requestedBy": "usr_alice",
      "requestedByEmail": "[email protected]",
      "status": "pending",           // pending | approved | rejected
      "note": null,
      "reviewedBy": null,
      "reviewedByEmail": null,
      "reviewedAt": null,
      "createdAt": "2026-04-24T10:00:00.000Z"
    }
  ]
}

Credentials Vault

Store shared secrets (API keys, tokens, credentials) at the org level so every install in the org can use them without each operator configuring them individually. Values are encrypted at rest with AES-256-GCM. Secret values are never returned after creation.

Pro plan required

These endpoints return 402 if the org is not on a Pro plan.

GET
/api/orgs/:slug/secrets

List all secrets (keys, ids, timestamps only — values never returned).

Requires: all members

POST
/api/orgs/:slug/secrets

Create or update a secret. Body: { key, value }. Key is normalised to UPPER_SNAKE_CASE. Upserts if the key already exists.

Requires: owner / admin

DELETE
/api/orgs/:slug/secrets/:key

Delete a secret by key. Returns 204 No Content.

Requires: owner / admin

// GET /api/orgs/:slug/secrets
{
  "secrets": [
    { "id": "sec_abc", "key": "OPENAI_API_KEY", "createdAt": "2026-04-01T00:00:00.000Z", "updatedAt": "2026-04-22T00:00:00.000Z" },
    { "id": "sec_def", "key": "SLACK_BOT_TOKEN", "createdAt": "2026-04-10T00:00:00.000Z", "updatedAt": "2026-04-10T00:00:00.000Z" }
  ]
}

// POST /api/orgs/:slug/secrets — create or update
{ "ok": true, "key": "OPENAI_API_KEY" }
Org secrets are automatically injected into all installs linked to the org at run time — they take lower priority than install-level secrets of the same name. Every create, update, and delete is recorded in the org audit log.

Org Billing

Manage an org's Pro subscription. The checkout endpoint creates a Stripe Checkout session (14-day free trial, card required). The portal endpoint returns a Stripe Billing Portal URL for plan changes and invoice history. All billing endpoints require owner or admin role except billing status, which is readable by all members.

GET
/api/orgs/:slug/billing/status

Returns current plan, subscription status, trial expiry, and Pro flag. Readable by all members.

Requires: all members

POST
/api/orgs/:slug/billing/checkout

Creates a Stripe Checkout session for upgrading to Pro. Returns { url } — redirect the user to this URL.

Requires: owner / admin

GET
/api/orgs/:slug/billing/portal

Returns a Stripe Billing Portal URL for managing payment method, cancelling, or viewing invoices.

Requires: owner / admin

POST
/api/orgs/:slug/billing/cancel

Schedules subscription cancellation at period end (no immediate effect). Returns { ok, message }.

Requires: owner only

// GET /api/orgs/:slug/billing/status
{
  "plan": "pro",
  "subscriptionStatus": "trialing",     // "active" | "trialing" | "past_due" | "cancelled" | null
  "trialEndsAt": "2026-05-18T00:00:00.000Z",
  "trialDaysLeft": 14,
  "hasCustomer": true,
  "planUpdatedAt": "2026-05-04T00:00:00.000Z",
  "isPro": true,
  "isTrialing": true
}

// POST /api/orgs/:slug/billing/checkout
{ "url": "https://checkout.stripe.com/c/pay/..." }

// GET /api/orgs/:slug/billing/portal
{ "url": "https://billing.stripe.com/p/session/..." }

Audit Log

Paginated audit trail of all actions taken within the org — member changes, secret creates/deletes, billing changes, governance decisions, and deployment approvals. Supports date range filtering. Returns newest events first.

Pro plan required

Returns 402 on free orgs.

GET /api/orgs/:slug/audit-log
Session · owner / admin
curl "https://openenvelope.org/api/orgs/acme-corp/audit-log?page=1&limit=50&from=2026-05-01" \
  --cookie "envelope_session=<token>"

Query parameters

pagePage number (1-based). Defaults to 1.
limitRecords per page (1–100). Defaults to 50.
fromISO 8601 date — only return events on or after this timestamp.
toISO 8601 date — only return events on or before this timestamp.
{
  "events": [
    {
      "id": "evt_abc",
      "actorId": "usr_alice",
      "actorEmail": "[email protected]",
      "action": "secret.created",          // e.g. secret.created | member.role_changed | billing.cancel_requested
      "meta": { "key": "OPENAI_API_KEY" },
      "createdAt": "2026-05-03T14:22:00.000Z"
    }
  ],
  "page": 1,
  "limit": 50
}

Analytics

Aggregate run statistics for all templates and pipeline installs owned by the org. Returns daily totals and a per-template breakdown. Free orgs are limited to a 30-day window; Pro orgs can query up to 365 days.

GET /api/orgs/:slug/analytics
Session · all members
curl "https://openenvelope.org/api/orgs/acme-corp/analytics?from=2026-04-01&to=2026-05-04" \
  --cookie "envelope_session=<token>"

Query parameters

fromISO 8601 start date. Defaults to 30 days ago.
toISO 8601 end date. Defaults to now.
{
  "fromDate": "2026-04-01T00:00:00.000Z",
  "toDate":   "2026-05-04T00:00:00.000Z",
  "isPro": true,
  "maxDays": 365,
  "totalRuns": 1284,
  "successRate": 97,                       // null if no runs in range
  "byDay": [
    { "date": "2026-04-01", "total": 42, "success": 41 },
    { "date": "2026-04-02", "total": 38, "success": 38 },
    ...
  ],
  "byTemplate": [
    { "templateId": "tmpl_abc", "name": "Outbound Sales Team", "total": 820, "success": 798 },
    { "templateId": "pipeline:inst_xyz:daily_report", "name": "daily_report › daily report", "total": 464, "success": 464 }
  ]
}

Transfer Ownership

Transfers org ownership to another existing member. The current owner is downgraded to member. Requires a confirmation string equal to the org name to prevent accidental transfers. Only the current owner can call this.

PATCH /api/orgs/:slug/transfer
Session · owner only
curl -X PATCH "https://openenvelope.org/api/orgs/acme-corp/transfer" \
  --cookie "envelope_session=<token>" \
  -H "Content-Type: application/json" \
  -d '{
    "newOwnerId": "usr_bob123",
    "confirmation": "Acme Corp"
  }'

Request body

newOwnerId

required

The user ID of an existing org member to promote to owner.
confirmation

required

Must exactly match the org's display name (case-sensitive). This prevents accidental transfers. Returns 400 if it doesn't match.
{ "ok": true }

// Errors:
// 400 { "error": "Confirmation must match the org name exactly" }
// 404 { "error": "Target user is not a member" }
PATCH

Cancel Deployment

Cancels an active install. Before marking the install as cancelled, Envelope automatically bills any unbilled usage since the last paid invoice — so the deployer is charged for work already done and the builder receives their share. Requires a session cookie.

Bill-to-date on cancel: The endpoint finds the last paid invoice's period end, counts completed runs since then, and charges the deployer if the amount is ≥ $0.50 (Stripe minimum). If there is no billable usage, the install is cancelled immediately with no charge.

A receipt email is sent to the deployer regardless of whether a charge was made.

PATCH /api/stripe/installs/:installId/cancel
Session Required
curl -X PATCH https://openenvelope.org/api/stripe/installs/inst_xyz/cancel \
  --cookie "envelope_session=<token>"

Response

// Cancelled with bill-to-date charge
{
  "status":      "cancelled",
  "charged":     true,
  "amountCents": 320
}

// Cancelled — no billable usage
{
  "status":      "cancelled",
  "charged":     false,
  "amountCents": 0
}
GET

Team JSON Schema

Returns the canonical JSON Schema (draft-07) for Envelope team definitions. Use this to validate team.json files locally or configure IDE tooling (VS Code, JetBrains, etc.) for autocomplete. The schema is served with a 24-hour cache header and CORS open to all origins.

GET /api/schema/team/v1.json
No Auth Required
curl https://openenvelope.org/api/schema/team/v1.json

VS Code tip: Add a $schema key to your team.json to get inline validation and autocomplete:

{
  "$schema": "https://openenvelope.org/api/schema/team/v1.json",
  "name": "My Pipeline",
  "agents": [ ... ]
}

Response

// Content-Type: application/schema+json
// Cache-Control: public, max-age=86400, immutable
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Envelope Team Definition",
  "type": "object",
  "properties": { ... }
}
POST

Webhook Trigger (no-auth)

Triggers a managed pipeline run using only the install's webhookToken — no Authorization header needed. The token IS the credential, designed for safe storage in external systems like Zapier, HubSpot workflows, Make, or n8n that need to fire a pipeline without managing API keys. The response is immediate; the run executes asynchronously. If callbackUrl is provided, Envelope POSTs the full result there when the run completes.

Finding your token: The webhookToken is returned in the My Managed Installs response. Treat it like a password — rotate it by re-deploying the install if compromised.

POST /api/webhook/:token
No Auth — token IS the credential
# Minimal — fire and forget
curl -X POST https://openenvelope.org/api/webhook/wh_abc123def456

# With context seeded into the first agent
curl -X POST https://openenvelope.org/api/webhook/wh_abc123def456 \
  -H "Content-Type: application/json" \
  -d '{ "inputText": "Focus on enterprise accounts in Q3" }'

# With callback — Envelope will POST the result here when done
curl -X POST https://openenvelope.org/api/webhook/wh_abc123def456 \
  -H "Content-Type: application/json" \
  -d '{
    "inputText": "Weekly digest run",
    "callbackUrl": "https://your-server.com/envelope-callback"
  }'

Request Body

FieldDescription
inputText

optional

Free-form context string seeded into the first agent's input. Useful for parameterising runs (e.g. the prospect name, date range, or task instructions).
callbackUrl

optional

A URL Envelope will POST to when the run finishes. Must be publicly reachable. Receives the full run result (see callback body below).

Immediate Response (200)

{
  "runId":  "e3f1a2b4-...",
  "status": "queued"
}

// Errors:
// 404 { "error": "Webhook token not found" }
// 409 { "error": "Install is not ready (status: pending)" }

Callback Body (POSTed to callbackUrl on completion)

{
  "runId":        "e3f1a2b4-...",
  "installId":    "inst_abc",
  "status":       "completed",   // "completed" | "paused" | "failed"
  "response":     "Here is your weekly digest…",
  "inputTokens":  1240,
  "outputTokens": 480
}

// If the run paused at a human gate:
{
  "runId":         "e3f1a2b4-...",
  "installId":     "inst_abc",
  "status":        "paused",
  "pausedAtGate":  "review-gate",
  "response":      null
}
GET

Builder Earnings

Returns a breakdown of revenue earned by the authenticated builder across all their published templates. Only counts paid invoices. Requires a session cookie (available to template authors only).

GET /api/stripe/my-earnings
Session Required
curl https://openenvelope.org/api/stripe/my-earnings \
  --cookie "envelope_session=<token>"

Response

{
  "totalBuilderAmountCents": 4850,
  "byTemplate": [
    {
      "templateId":          "tmpl_abc",
      "name":                "Outreach Sequencer",
      "slug":                "outreach-sequencer",
      "paidCount":           12,
      "builderAmountCents":  3600,
      "lastPaidAt":          "2025-04-01T00:00:00.000Z"
    },
    {
      "templateId":          "tmpl_def",
      "name":                "Lead Enricher",
      "slug":                "lead-enricher",
      "paidCount":           4,
      "builderAmountCents":  1250,
      "lastPaidAt":          "2025-03-01T00:00:00.000Z"
    }
  ]
}
FieldDescription
totalBuilderAmountCentsSum of all builder payouts across all templates, in cents.
byTemplate[].paidCountNumber of paid invoices for this template.
byTemplate[].builderAmountCentsTotal builder payout for this template, in cents. Divide by 100 for dollars.
byTemplate[].lastPaidAtISO-8601 timestamp of the most recent paid invoice period start.
GET

Current User

Returns the profile of the currently authenticated session user. Useful for confirming the identity behind a session cookie or building account pages. Returns 401 if no valid session exists.

GET /api/auth/me
Session Required
curl https://openenvelope.org/api/auth/me \
  --cookie "envelope_session=<token>"

Response

{
  "id":              "a1b2c3d4",
  "email":           "[email protected]",
  "name":            "Alice Chen",
  "avatarUrl":       "https://avatars.githubusercontent.com/u/...",
  "githubConnected": true,
  "handle":          "alice",
  "handleLocked":    false
}

// Errors:
// 401 { "error": "Not authenticated" }
// 401 { "error": "Session expired" }
FieldDescription
handleURL-safe username (e.g. alice). null if not yet set.
handleLockedtrue once the handle has been changed once — it cannot be changed again via the API.
githubConnectedWhether a GitHub OAuth identity is linked to this account.
PATCH

Set Handle

Sets the authenticated user's public handle (URL-safe username). Handles are one-time changes — once set, they cannot be updated again via the API. The handle is lowercased, stripped of invalid characters, and truncated to 24 characters. Returns 409 if already taken by another account, and 403 if the handle has already been changed once.

PATCH /api/auth/me
Session Required
curl -X PATCH https://openenvelope.org/api/auth/me \
  --cookie "envelope_session=<token>" \
  -H "Content-Type: application/json" \
  -d '{ "handle": "alice-chen" }'
{ "ok": true, "handle": "alice-chen" }

// Errors:
// 400 { "error": "Handle is required" }
// 400 { "error": "Handle must be at least 2 characters" }
// 403 { "error": "Handle can only be changed once" }
// 409 { "error": "That handle is already taken" }
GET

List API Keys

Returns all personal API keys belonging to the authenticated session user, newest first. Key values are included in full — treat the response as sensitive.

GET /api/auth/keys
Session Required
curl https://openenvelope.org/api/auth/keys \
  --cookie "envelope_session=<token>"
{
  "keys": [
    {
      "id":        "k1a2b3c4",
      "key":       "env_live_a1b2c3d4e5f6...",
      "label":     "Production key",
      "createdAt": "2025-03-15T10:22:00.000Z"
    }
  ]
}
POST

Create API Key

Creates a new personal API key for the authenticated session user. The raw key value is returned only on creation — it cannot be retrieved again. Keys follow the format env_live_<32 hex chars>.

POST /api/auth/keys
Session Required
curl -X POST https://openenvelope.org/api/auth/keys \
  --cookie "envelope_session=<token>" \
  -H "Content-Type: application/json" \
  -d '{ "label": "CI/CD pipeline key" }'

Request Body

FieldDescription
label

optional

Human-readable name for the key (e.g. "CI key", "Zapier integration").
installId

optional

Associate this key with a specific install for scoped access.
// 201 Created
{
  "id":        "k1a2b3c4",
  "key":       "env_live_a1b2c3d4e5f6...",
  "label":     "CI/CD pipeline key",
  "installId": null,
  "createdAt": "2025-05-01T12:00:00.000Z",
  "note":      "Store this key securely — it will not be shown again."
}
DELETE

Delete API Key

Permanently revokes an API key. Any integrations using the deleted key will immediately lose access. Returns 204 No Content on success.

DELETE /api/auth/keys/:id
Session Required
curl -X DELETE https://openenvelope.org/api/auth/keys/k1a2b3c4 \
  --cookie "envelope_session=<token>"
// 204 No Content (empty body on success)

// Errors:
// 404 { "error": "Key not found" }
// 403 { "error": "Forbidden" }
GET

Resolve Key Identity

Resolves an API key to the name of its owner and their primary organization. Useful in deploy wizards, CLI tools, or onboarding flows where you want to confirm the key belongs to the expected user before proceeding. Requires a valid API key in the Authorization header.

GET /api/auth/key-identity
API Key Required
curl https://openenvelope.org/api/auth/key-identity \
  -H "Authorization: Bearer $ENVELOPE_KEY"

Response

{
  "userName": "Alice Chen",
  "orgName":  "Acme Corp",
  "orgSlug":  "acme-corp"
}

// Key with no org membership:
{
  "userName": "Alice Chen",
  "orgName":  null,
  "orgSlug":  null
}

// Errors:
// 401 { "error": "Invalid API key" }