Skip to main content
    API v2026-02-17

    Keypra API

    Execute your workflows, compositions, skills, and personas programmatically. Build AI-powered pipelines with a single API call.

    Machine-readable resources

    Every discovery document lives at a predictable, stable URL so agents and tooling can find the API without scraping this page.

    ResourceURL
    OpenAPI 3.1 (JSON)/openapi.json
    OpenAPI 3.1 (YAML)/api/openapi.yaml
    API catalog (RFC 9727)/.well-known/api-catalog
    Public API base/api/public/v1
    API health/api/public/v1/health
    MCP server/api/public/mcp
    Agent skills index/.well-known/agent-skills/index.json
    Markdown gateway/api/public/markdown-gateway
    LLM site summary/llms.txt

    Allowlisted pages also honour Accept: text/markdown directly on their canonical URL, and all negotiated responses send Vary: Accept, Accept-Encoding.

    ChatGPT & MCP

    Connect Keypra to ChatGPT, Claude, or any MCP client to get instant prompt health checks inside your conversations — then jump into Keypra to refine.

    MCP server: https://keypra.com/api/public/mcp

    No API key required. The analyze_prompt tool scores any prompt for clarity, role, task, context, format, and constraints, then returns a link to continue refining in Keypra.

    # In ChatGPT, after adding the Keypra MCP server:
    "Check this prompt with Keypra: Write a concise email to my team about the Q3 budget review."

    Quick Start

    1

    Get your API key

    Expert tier only

    Go to Settings → API Access and create a key. The Keypra REST API is reserved for the Expert plan — Builder and Free plans cannot create or use API keys.

    2

    Make your first call

    curl https://keypra.com/api/public/v1/workflows \
      -H "X-API-Key: mdi_your_key_here"

    Authentication

    All requests require an X-API-Key header with a valid Keypra API key (prefix mdi_).

    Keys are SHA-256 hashed at rest. Each key has configurable scopes and rate limits.

    Credits: API calls consume credits from the same pool as the UI (1 credit = 1,000 tokens). Check X-Credits-Remaining in response headers.

    Available Scopes

    ScopeGrants Access To
    execute:workflowGET /workflows, POST /workflows/:id/execute
    execute:compositionGET /compositions, POST /compositions/:id/execute
    execute:skillGET /skills, POST /skills/:id/execute
    execute:personaGET /personas, POST /personas/:id/execute

    Endpoint Reference

    All responses include X-API-Version, X-RateLimit-*, and execution endpoints also include X-Credits-Remaining.

    GET
    /workflows

    List all your workflows

    Scope: execute:workflow

    Response Example

    {
      "success": true,
      "data": {
        "items": [
          {
            "id": "uuid",
            "name": "Content Pipeline",
            "description": "Multi-step content generation",
            "category": "content",
            "usage_count": 42,
            "created_at": "2026-02-13T10:00:00Z"
          }
        ],
        "count": 1
      }
    }
    POST
    /workflows/:id/execute

    Execute a workflow with runtime variables

    Scope: execute:workflow

    Path Parameters

    NameTypeDescription
    iduuidWorkflow ID

    Request Body Fields

    FieldTypeRequiredDescription
    variablesobjectKey-value pairs injected into workflow nodes
    include_tracebooleanInclude per-node execution trace (default: false)

    Request Body Example

    {
      "variables": { "topic": "AI trends" },
      "include_trace": true
    }

    Response Example

    {
      "success": true,
      "data": {
        "run_id": "uuid",
        "status": "completed",
        "outputs": { "summary": "..." },
        "node_results": [ ... ],
        "duration_ms": 3200,
        "token_usage": { "input_tokens": 800, "output_tokens": 1500, "total_tokens": 2300 }
      }
    }
    GET
    /compositions

    List all your prompt compositions

    Scope: execute:composition

    Response Example

    {
      "success": true,
      "data": {
        "items": [
          { "id": "uuid", "name": "Blog Writer", "description": "...", "tags": ["content"], "times_used": 15 }
        ],
        "count": 1
      }
    }
    POST
    /compositions/:id/execute

    Execute a composition with variable substitution

    Scope: execute:composition

    Path Parameters

    NameTypeDescription
    iduuidComposition ID

    Request Body Fields

    FieldTypeRequiredDescription
    variablesobjectVariable substitution values
    modelstringModel override (tier-gated)

    Request Body Example

    {
      "variables": { "audience": "developers" },
      "model": "openai/gpt-5-mini"
    }

    Response Example

    {
      "success": true,
      "data": {
        "content": "Generated text...",
        "model_used": "openai/gpt-5-mini",
        "token_usage": { "input_tokens": 450, "output_tokens": 1200, "total_tokens": 1650 },
        "duration_ms": 2100
      }
    }
    GET
    /skills

    List all your custom skills

    Scope: execute:skill

    Response Example

    {
      "success": true,
      "data": {
        "items": [
          { "id": "uuid", "name": "Feedback Analyzer", "category": "analysis" }
        ],
        "count": 1
      }
    }
    POST
    /skills/:id/execute

    Execute a skill with context input

    Scope: execute:skill

    Path Parameters

    NameTypeDescription
    iduuidSkill ID

    Request Body Fields

    FieldTypeRequiredDescription
    contextstringInput text for the skill to process
    modelstringModel override (tier-gated)

    Request Body Example

    {
      "context": "Analyze this customer feedback: ...",
      "model": "google/gemini-2.5-flash"
    }

    Response Example

    {
      "success": true,
      "data": {
        "content": "Analysis result...",
        "skill_name": "Feedback Analyzer",
        "model_used": "google/gemini-2.5-flash",
        "token_usage": { "input_tokens": 300, "output_tokens": 900, "total_tokens": 1200 },
        "duration_ms": 1800
      }
    }
    GET
    /personas

    List all your AI personas

    Scope: execute:persona

    Response Example

    {
      "success": true,
      "data": {
        "items": [
          {
            "id": "uuid",
            "name": "Corporate Strategy Analyst",
            "emoji": "🏢",
            "description": "Expert in strategic planning",
            "content_preview": "You are a corporate strategy analyst..."
          }
        ],
        "count": 1
      }
    }
    POST
    /personas/:id/execute

    Execute a persona with user input

    Scope: execute:persona

    Path Parameters

    NameTypeDescription
    iduuidPersona ID

    Request Body Fields

    FieldTypeRequiredDescription
    inputstringUser input for the persona to process
    contextstringAdditional context appended to input
    modelstringModel override (tier-gated)
    temperaturenumberTemperature for AI response (0-1)

    Request Body Example

    {
      "input": "Analyze this quarterly report...",
      "context": "Additional context here...",
      "model": "google/gemini-2.5-flash",
      "temperature": 0.7
    }

    Response Example

    {
      "success": true,
      "data": {
        "content": "Strategic analysis result...",
        "persona_name": "Corporate Strategy Analyst",
        "model_used": "google/gemini-2.5-flash",
        "token_usage": { "input_tokens": 245, "output_tokens": 890, "total_tokens": 1135 },
        "duration_ms": 2340
      }
    }

    OpenAPI Specification

    Import the spec into Postman, Insomnia, or use it to generate typed SDKs in any language.

    keypra-openapi.yaml
    openapi: "3.1.0"
    info:
      title: Keypra API
      version: "2026-05-05"
      description: |
        Execute your workflows, compositions, and skills programmatically.
        Build AI-powered pipelines with a single API call.
      contact:
        name: Keypra Support
        url: https://keypra.com/help
    
    servers:
      - url: https://keypra.com/api/public/v1
        description: Production
    
    security:
      - apiKey: []
    
    paths:
      /workflows:
        get:
          operationId: listWorkflows
          summary: List workflows
          description: Returns all workflows owned by the authenticated user.
          tags: [Workflows]
          responses:
            "200":
              description: Workflow list
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/WorkflowListResponse"
              headers:
                X-RateLimit-Limit:
                  $ref: "#/components/headers/X-RateLimit-Limit"
                X-RateLimit-Remaining:
                  $ref: "#/components/headers/X-RateLimit-Remaining"
                X-RateLimit-Reset:
                  $ref: "#/components/headers/X-RateLimit-Reset"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /workflows/{id}/execute:
        post:
          operationId: executeWorkflow
          summary: Execute a workflow
          description: Run a workflow with runtime variables. Returns outputs and optional trace.
          tags: [Workflows]
          parameters:
            - name: id
              in: path
              required: true
              schema:
                type: string
                format: uuid
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/ExecuteWorkflowRequest"
          responses:
            "200":
              description: Execution result
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/ExecuteWorkflowResponse"
              headers:
                X-RateLimit-Limit:
                  $ref: "#/components/headers/X-RateLimit-Limit"
                X-RateLimit-Remaining:
                  $ref: "#/components/headers/X-RateLimit-Remaining"
                X-RateLimit-Reset:
                  $ref: "#/components/headers/X-RateLimit-Reset"
                X-Credits-Remaining:
                  $ref: "#/components/headers/X-Credits-Remaining"
            "400":
              $ref: "#/components/responses/ValidationError"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "402":
              $ref: "#/components/responses/InsufficientCredits"
            "404":
              $ref: "#/components/responses/NotFound"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /compositions:
        get:
          operationId: listCompositions
          summary: List compositions
          description: Returns all prompt compositions owned by the authenticated user.
          tags: [Compositions]
          responses:
            "200":
              description: Composition list
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/CompositionListResponse"
              headers:
                X-RateLimit-Limit:
                  $ref: "#/components/headers/X-RateLimit-Limit"
                X-RateLimit-Remaining:
                  $ref: "#/components/headers/X-RateLimit-Remaining"
                X-RateLimit-Reset:
                  $ref: "#/components/headers/X-RateLimit-Reset"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /compositions/{id}/execute:
        post:
          operationId: executeComposition
          summary: Execute a composition
          description: Run a prompt composition with variable substitution and optional model override.
          tags: [Compositions]
          parameters:
            - name: id
              in: path
              required: true
              schema:
                type: string
                format: uuid
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/ExecuteCompositionRequest"
          responses:
            "200":
              description: Execution result
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/ExecuteCompositionResponse"
              headers:
                X-RateLimit-Limit:
                  $ref: "#/components/headers/X-RateLimit-Limit"
                X-RateLimit-Remaining:
                  $ref: "#/components/headers/X-RateLimit-Remaining"
                X-RateLimit-Reset:
                  $ref: "#/components/headers/X-RateLimit-Reset"
                X-Credits-Remaining:
                  $ref: "#/components/headers/X-Credits-Remaining"
            "400":
              $ref: "#/components/responses/ValidationError"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "402":
              $ref: "#/components/responses/InsufficientCredits"
            "403":
              $ref: "#/components/responses/ModelAccessDenied"
            "404":
              $ref: "#/components/responses/NotFound"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /skills:
        get:
          operationId: listSkills
          summary: List skills
          description: Returns all custom skills owned by the authenticated user.
          tags: [Skills]
          responses:
            "200":
              description: Skill list
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/SkillListResponse"
              headers:
                X-RateLimit-Limit:
                  $ref: "#/components/headers/X-RateLimit-Limit"
                X-RateLimit-Remaining:
                  $ref: "#/components/headers/X-RateLimit-Remaining"
                X-RateLimit-Reset:
                  $ref: "#/components/headers/X-RateLimit-Reset"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /skills/{id}/execute:
        post:
          operationId: executeSkill
          summary: Execute a skill
          description: Run a skill with context input and optional model override.
          tags: [Skills]
          parameters:
            - name: id
              in: path
              required: true
              schema:
                type: string
                format: uuid
          requestBody:
            required: true
            description: |
              Provide either `input` (preferred, matches SDK) or `context` (legacy alias).
              If both are sent, `input` wins.
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/ExecuteSkillRequest"
          responses:
            "200":
              description: Execution result
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/ExecuteSkillResponse"
              headers:
                X-RateLimit-Limit:
                  $ref: "#/components/headers/X-RateLimit-Limit"
                X-RateLimit-Remaining:
                  $ref: "#/components/headers/X-RateLimit-Remaining"
                X-RateLimit-Reset:
                  $ref: "#/components/headers/X-RateLimit-Reset"
                X-Credits-Remaining:
                  $ref: "#/components/headers/X-Credits-Remaining"
            "400":
              $ref: "#/components/responses/ValidationError"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "402":
              $ref: "#/components/responses/InsufficientCredits"
            "403":
              $ref: "#/components/responses/ModelAccessDenied"
            "404":
              $ref: "#/components/responses/NotFound"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /personas:
        get:
          operationId: listPersonas
          summary: List personas
          description: Returns all personas owned by the authenticated user.
          tags: [Personas]
          responses:
            "200":
              description: Persona list
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/PersonaListResponse"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /personas/{id}/execute:
        post:
          operationId: executePersona
          summary: Execute a persona conversation
          description: Run a persona with a user message and optional model override.
          tags: [Personas]
          parameters:
            - name: id
              in: path
              required: true
              schema:
                type: string
                format: uuid
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/ExecutePersonaRequest"
          responses:
            "200":
              description: Persona response
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/ExecutePersonaResponse"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "402":
              $ref: "#/components/responses/InsufficientCredits"
            "404":
              $ref: "#/components/responses/NotFound"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /judge/run:
        post:
          operationId: runJudge
          summary: Run the Judgement workflow against a prompt
          description: |
            Evaluates a prompt against optional criteria and returns a structured
            verdict + score. Expert tier only.
          tags: [Judge]
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/JudgeRunRequest"
          responses:
            "200":
              description: Judgement verdict
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/JudgeRunResponse"
            "401":
              $ref: "#/components/responses/Unauthorized"
            "402":
              $ref: "#/components/responses/InsufficientCredits"
            "429":
              $ref: "#/components/responses/RateLimited"
    
      /sanitiser/scan:
        post:
          operationId: sanitiserScan
          summary: Scan text against the org Prompt Sanitiser ruleset
          description: |
            Returns matched findings (PII, secrets, custom org rules) with severity.
            Non-AI; deterministic. Expert tier only.
          tags: [Sanitiser]
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/SanitiserScanRequest"
          responses:
            "200":
              description: Sanitiser findings
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/SanitiserScanResponse"
            "401":
              $ref: "#/components/responses/Unauthorized"
    
      /context-cards:
        get:
          operationId: listContextCards
          summary: List the caller's Context Cards
          description: RLS-scoped — only returns cards owned by the API key holder.
          tags: [ContextCards]
          responses:
            "200":
              description: Context card list
              content:
                application/json:
                  schema:
                    $ref: "#/components/schemas/ListResponse"
            "401":
              $ref: "#/components/responses/Unauthorized"
        post:
          operationId: createContextCard
          summary: Create a Context Card
          tags: [ContextCards]
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/ContextCardWriteRequest"
          responses:
            "201":
              description: Created context card
            "401":
              $ref: "#/components/responses/Unauthorized"
    
      /context-cards/{id}:
        get:
          operationId: getContextCard
          summary: Fetch a single Context Card
          tags: [ContextCards]
          parameters:
            - name: id
              in: path
              required: true
              schema:
                type: string
                format: uuid
          responses:
            "200":
              description: Context card
            "404":
              $ref: "#/components/responses/NotFound"
        patch:
          operationId: updateContextCard
          summary: Update a Context Card
          tags: [ContextCards]
          parameters:
            - name: id
              in: path
              required: true
              schema:
                type: string
                format: uuid
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: "#/components/schemas/ContextCardWriteRequest"
          responses:
            "200":
              description: Updated card
            "404":
              $ref: "#/components/responses/NotFound"
        delete:
          operationId: deleteContextCard
          summary: Delete a Context Card
          tags: [ContextCards]
          parameters:
            - name: id
              in: path
              required: true
              schema:
                type: string
                format: uuid
          responses:
            "204":
              description: Deleted
            "404":
              $ref: "#/components/responses/NotFound"
    
    components:
      securitySchemes:
        apiKey:
          type: apiKey
          in: header
          name: X-API-Key
          description: Keypra API key (prefix mdi_)
    
      headers:
        X-RateLimit-Limit:
          description: Maximum requests per minute for your tier
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Remaining requests in the current window
          schema:
            type: integer
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit resets
          schema:
            type: integer
        X-Credits-Remaining:
          description: Remaining token credits after this request
          schema:
            type: integer
    
      schemas:
        TokenUsage:
          type: object
          properties:
            input_tokens:
              type: integer
            output_tokens:
              type: integer
            total_tokens:
              type: integer
    
        ErrorBody:
          type: object
          properties:
            success:
              type: boolean
              example: false
            error:
              type: object
              properties:
                code:
                  type: string
                  example: INSUFFICIENT_CREDITS
                message:
                  type: string
    
        WorkflowListItem:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            description:
              type: string
            category:
              type: string
            usage_count:
              type: integer
            created_at:
              type: string
              format: date-time
    
        WorkflowListResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                items:
                  type: array
                  items:
                    $ref: "#/components/schemas/WorkflowListItem"
                count:
                  type: integer
    
        ExecuteWorkflowRequest:
          type: object
          properties:
            variables:
              type: object
              additionalProperties: true
              description: Runtime variables to inject into the workflow
            include_trace:
              type: boolean
              default: false
              description: Include per-node execution trace
    
        ExecuteWorkflowResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                run_id:
                  type: string
                  format: uuid
                status:
                  type: string
                  enum: [completed, failed]
                outputs:
                  type: object
                node_results:
                  type: array
                  items:
                    type: object
                duration_ms:
                  type: integer
                token_usage:
                  $ref: "#/components/schemas/TokenUsage"
    
        CompositionListItem:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            description:
              type: string
            tags:
              type: array
              items:
                type: string
            times_used:
              type: integer
            created_at:
              type: string
              format: date-time
    
        CompositionListResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                items:
                  type: array
                  items:
                    $ref: "#/components/schemas/CompositionListItem"
                count:
                  type: integer
    
        ExecuteCompositionRequest:
          type: object
          properties:
            variables:
              type: object
              additionalProperties: true
            model:
              type: string
              description: "Model override (e.g. openai/gpt-5-mini)"
    
        ExecuteCompositionResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                content:
                  type: string
                model_used:
                  type: string
                token_usage:
                  $ref: "#/components/schemas/TokenUsage"
                duration_ms:
                  type: integer
    
        SkillListItem:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            description:
              type: string
            category:
              type: string
            created_at:
              type: string
              format: date-time
    
        SkillListResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                items:
                  type: array
                  items:
                    $ref: "#/components/schemas/SkillListItem"
                count:
                  type: integer
    
        ExecuteSkillRequest:
          description: |
            Provide either `input` (preferred, matches SDK) or `context` (legacy alias).
            If both are sent, `input` wins.
          oneOf:
            - type: object
              required: [input]
              properties:
                input:
                  type: string
                  description: Input text for the skill to process.
                model:
                  type: string
                  description: "Model override (e.g. google/gemini-2.5-flash)"
                variables:
                  type: object
                  additionalProperties: true
            - type: object
              required: [context]
              properties:
                context:
                  type: string
                  description: Legacy alias for `input`.
                model:
                  type: string
                variables:
                  type: object
                  additionalProperties: true
    
        ExecuteSkillResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                content:
                  type: string
                skill_name:
                  type: string
                model_used:
                  type: string
                token_usage:
                  $ref: "#/components/schemas/TokenUsage"
                duration_ms:
                  type: integer
    
        ListResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                items:
                  type: array
                  items:
                    type: object
                    additionalProperties: true
                count:
                  type: integer
    
        PersonaListItem:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            description:
              type: string
            emoji:
              type: string
            created_at:
              type: string
              format: date-time
    
        PersonaListResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                items:
                  type: array
                  items:
                    $ref: "#/components/schemas/PersonaListItem"
                count:
                  type: integer
    
        ExecutePersonaRequest:
          type: object
          required: [input]
          properties:
            input:
              type: string
              description: User message to send to the persona.
            context:
              type: string
              description: Optional extra context appended to the user message.
            model:
              type: string
            temperature:
              type: number
    
        ExecutePersonaResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                content:
                  type: string
                persona_name:
                  type: string
                model_used:
                  type: string
                token_usage:
                  $ref: "#/components/schemas/TokenUsage"
    
        JudgeRunRequest:
          type: object
          required: [prompt]
          properties:
            prompt:
              type: string
            criteria:
              type: string
            model:
              type: string
    
        JudgeRunResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                verdict:
                  type: string
                  enum: [pass, fail, needs_revision]
                score:
                  type: number
                  minimum: 0
                  maximum: 100
                reasoning:
                  type: string
                suggestions:
                  type: array
                  items:
                    type: string
    
        SanitiserScanRequest:
          type: object
          required: [text]
          properties:
            text:
              type: string
    
        SanitiserScanResponse:
          type: object
          properties:
            success:
              type: boolean
            data:
              type: object
              properties:
                findings:
                  type: array
                  items:
                    type: object
                    properties:
                      rule_id:
                        type: string
                      label:
                        type: string
                      severity:
                        type: string
                        enum: [info, warn, block]
                      match:
                        type: string
                      start:
                        type: integer
                      end:
                        type: integer
                severity:
                  type: string
                  enum: [clean, info, warn, block]
    
        ContextCardWriteRequest:
          type: object
          required: [title, content]
          properties:
            title:
              type: string
            content:
              type: string
            category:
              type: string
            tags:
              type: array
              items:
                type: string
    
      responses:
        Unauthorized:
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorBody"
        RateLimited:
          description: Rate limit exceeded
          headers:
            Retry-After:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorBody"
        InsufficientCredits:
          description: Not enough credits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorBody"
        ModelAccessDenied:
          description: Model not available on your tier
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorBody"
        NotFound:
          description: Resource not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorBody"
        ValidationError:
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorBody"
    

    SDK Wrappers

    Copy these self-contained client classes into your project. They include automatic retry on 429, typed errors, and credit tracking via response headers.

    import requests
    import time
    
    class KeypraError(Exception):
        def __init__(self, code: str, message: str, status: int):
            self.code = code
            self.message = message
            self.status = status
            super().__init__(f"{code}: {message}")
    
    class RateLimitError(KeypraError):
        def __init__(self, message: str, retry_after: int = 60):
            self.retry_after = retry_after
            super().__init__("RATE_LIMITED", message, 429)
    
    class InsufficientCreditsError(KeypraError):
        def __init__(self, message: str):
            super().__init__("INSUFFICIENT_CREDITS", message, 402)
    
    class KeypraClient:
        """Keypra API client with automatic retry and credit monitoring."""
    
        BASE_URL = "https://keypra.com/api/public/v1"
    
        def __init__(self, api_key: str, max_retries: int = 3):
            self.api_key = api_key
            self.max_retries = max_retries
            self.credits_remaining = None
            self._session = requests.Session()
            self._session.headers.update({
                "X-API-Key": api_key,
                "Content-Type": "application/json",
            })
    
        def _request(self, method: str, path: str, **kwargs) -> dict:
            url = f"{self.BASE_URL}{path}"
            for attempt in range(self.max_retries + 1):
                resp = self._session.request(method, url, **kwargs)
    
                # Update credit balance from headers
                cr = resp.headers.get("X-Credits-Remaining")
                if cr is not None:
                    self.credits_remaining = int(cr)
    
                if resp.status_code == 429:
                    if attempt < self.max_retries:
                        wait = int(resp.headers.get("Retry-After", 60))
                        time.sleep(wait)
                        continue
                    raise RateLimitError(resp.json()["error"]["message"])
    
                if resp.status_code == 402:
                    raise InsufficientCreditsError(resp.json()["error"]["message"])
    
                if not resp.ok:
                    body = resp.json()
                    raise KeypraError(
                        body["error"]["code"],
                        body["error"]["message"],
                        resp.status_code,
                    )
    
                return resp.json()
    
        # ── Workflows ──────────────────────────────────────────
        def list_workflows(self) -> dict:
            return self._request("GET", "/workflows")
    
        def execute_workflow(
            self, workflow_id: str, variables: dict = None, include_trace: bool = False
        ) -> dict:
            return self._request(
                "POST",
                f"/workflows/{workflow_id}/execute",
                json={"variables": variables or {}, "include_trace": include_trace},
            )
    
        # ── Compositions ───────────────────────────────────────
        def list_compositions(self) -> dict:
            return self._request("GET", "/compositions")
    
        def execute_composition(
            self, composition_id: str, variables: dict = None, model: str = None
        ) -> dict:
            body: dict = {"variables": variables or {}}
            if model:
                body["model"] = model
            return self._request(
                "POST", f"/compositions/{composition_id}/execute", json=body
            )
    
        # ── Skills ─────────────────────────────────────────────
        def list_skills(self) -> dict:
            return self._request("GET", "/skills")
    
        def execute_skill(
            self, skill_id: str, input: str, model: str = None
        ) -> dict:
            # 'input' is the preferred field (matches OpenAPI). 'context' is
            # accepted as a legacy alias server-side.
            body: dict = {"input": input}
            if model:
                body["model"] = model
            return self._request(
                "POST", f"/skills/{skill_id}/execute", json=body
            )
    
        # ── Personas ───────────────────────────────────────────
        def list_personas(self) -> dict:
            return self._request("GET", "/personas")
    
        def execute_persona(
            self, persona_id: str, input: str, model: str = None
        ) -> dict:
            body: dict = {"input": input}
            if model:
                body["model"] = model
            return self._request(
                "POST", f"/personas/{persona_id}/execute", json=body
            )
    
    
    # ── Usage ──────────────────────────────────────────────────
    # client = KeypraClient("mdi_your_key_here")
    # workflows = client.list_workflows()
    # result = client.execute_workflow("uuid", {"topic": "AI trends"})
    # print(f"Credits left: {client.credits_remaining}")

    Tutorials

    Step-by-step guides for common API use cases.

    Tutorial 1

    Building a Content Pipeline

    Chain workflow executions to build an automated content generation pipeline — from research to final article.

    1

    Create your workflow in the Keypra UI

    Build a multi-node workflow in the visual editor. For this example, create a "Content Pipeline" workflow with variables: topic, audience, and tone.

    2

    List workflows to get the ID

    from keypra_client import KeypraClient
    
    client = KeypraClient("mdi_your_key_here")
    workflows = client.list_workflows()
    
    for wf in workflows["data"]["items"]:
        print(f'{wf["name"]}: {wf["id"]}')
    3

    Execute with variables

    result = client.execute_workflow(
        "YOUR_WORKFLOW_ID",
        variables={
            "topic": "AI agents in 2026",
            "audience": "technical leaders",
            "tone": "authoritative"
        },
        include_trace=True
    )
    
    print(f"Status: {result['data']['status']}")
    print(f"Duration: {result['data']['duration_ms']}ms")
    print(f"Credits remaining: {client.credits_remaining}")
    4

    Chain outputs into a second workflow

    Use the output of one workflow as input to the next — for example, feeding a research summary into an article writer.

    # Step 1: Research
    research = client.execute_workflow("RESEARCH_WF_ID", variables={"topic": "AI agents"})
    summary = research["data"]["outputs"]["summary"]
    
    # Step 2: Write article using research output
    article = client.execute_workflow("WRITER_WF_ID", variables={
        "research_summary": summary,
        "word_count": 1500,
        "style": "blog post"
    })
    
    print(article["data"]["outputs"]["article"])
    Tutorial 2

    Running Persona Voting

    Execute a multi-persona composition and aggregate voting results programmatically.

    1

    Set up your composition with multiple personas

    In the Keypra Composition Builder, create a composition that includes multiple context cards (personas). Each persona will evaluate the same prompt from its perspective.

    2

    Execute the composition via API

    result = client.execute_composition(
        "YOUR_COMPOSITION_ID",
        variables={
            "proposal": "Launch a freemium tier with 50 free API calls/month",
            "question": "Should we proceed? Vote YES or NO and explain."
        },
        model="openai/gpt-5-mini"
    )
    
    print(result["data"]["content"])
    3

    Parse and aggregate voting results

    import json
    
    # Run multiple compositions for different persona groups
    persona_groups = ["EXEC_COMP_ID", "TECH_COMP_ID", "MARKETING_COMP_ID"]
    votes = {"YES": 0, "NO": 0, "reasons": []}
    
    for comp_id in persona_groups:
        result = client.execute_composition(comp_id, variables={
            "proposal": "Launch a freemium API tier",
            "question": "Vote YES or NO with reasoning. Respond as JSON."
        })
        
        # Parse structured response
        content = result["data"]["content"]
        try:
            parsed = json.loads(content)
            votes[parsed["vote"]] += 1
            votes["reasons"].append({
                "group": comp_id,
                "vote": parsed["vote"],
                "reasoning": parsed["reasoning"]
            })
        except json.JSONDecodeError:
            print(f"Warning: Could not parse response from {comp_id}")
    
    print(f"Results: YES={votes['YES']}, NO={votes['NO']}")
    print(f"Credits remaining: {client.credits_remaining}")
    Tutorial 3

    Direct Persona Execution

    Execute AI personas directly via API — no composition wrapper needed. Use personas as standalone AI roles in external tools.

    1

    List your personas

    Personas are context cards with the "persona" category. List them to find the ID you need.

    personas = client.list_personas()
    
    for p in personas["data"]["items"]:
        print(f'{p["emoji"]} {p["name"]}: {p["id"]}')
    2

    Execute a persona with user input

    The persona's system prompt is used as the AI's role instruction. Your input becomes the user message.

    result = client.execute_persona(
        "YOUR_PERSONA_ID",
        input="Review this email draft for tone and clarity...",
        context="The email is going to a C-level executive.",
        model="google/gemini-2.5-flash",
        temperature=0.7
    )
    
    print(f"Persona: {result['data']['persona_name']}")
    print(f"Response: {result['data']['content']}")
    print(f"Tokens: {result['data']['token_usage']['total_tokens']}")
    3

    Multi-persona analysis pattern

    Run the same input through multiple personas to get diverse perspectives — without building a composition.

    persona_ids = ["STRATEGIST_ID", "LEGAL_ID", "MARKETING_ID"]
    proposal = "Launch a new AI-powered feature for enterprise clients."
    
    perspectives = []
    for pid in persona_ids:
        result = client.execute_persona(pid, input=proposal)
        perspectives.append({
            "persona": result["data"]["persona_name"],
            "analysis": result["data"]["content"]
        })
    
    for p in perspectives:
        print(f"\n--- {p['persona']} ---")
        print(p["analysis"])

    API Playground

    Test API endpoints live. Paste your API key, select an endpoint, fill in parameters, and execute.

    Sign in to use the API playground. Log in →

    Paste the full API key you received when you created it. Keys are not stored in the browser.

    No parameters required for this endpoint.

    Rate Limits & Pricing

    API access is an Expert-tier feature. Credits are shared with the UI — no separate API pool. Downgrading from Expert revokes all API keys after a 7-day grace period.

    FeatureExpert
    Requests / minute120
    Requests / day20,000
    API keys5
    Credit poolShared with UI
    ModelsAll models incl. GPT-5.2
    Webhook callbacks
    Endpointsworkflows, compositions, skills, personas, judge, sanitiser, context-cards

    Response Headers

    X-API-Version: 2026-02-13
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 47
    X-RateLimit-Reset: 1739462400
    X-Credits-Remaining: 85200

    Retry Strategy

    When you receive a 429 response, read the Retry-After header (seconds) and wait before retrying. The SDK wrappers above handle this automatically with configurable max retries.

    # Retry-After example
    if response.status_code == 429:
        wait = int(response.headers.get("Retry-After", 60))
        time.sleep(wait)
        response = session.request(method, url, **kwargs)  # retry

    Error Handling

    All errors return a structured JSON body with a machine-readable code field.

    {
      "success": false,
      "error": {
        "code": "INSUFFICIENT_CREDITS",
        "message": "Insufficient credits. Available: 200, minimum required: 500"
      }
    }
    CodeStatusDescription
    MISSING_API_KEY
    401
    No X-API-Key header provided
    INVALID_API_KEY
    401
    Key is invalid, expired, or revoked
    RATE_LIMITED
    429
    Per-minute rate limit exceeded
    DAILY_LIMIT_EXCEEDED
    429
    Daily request limit exceeded
    INSUFFICIENT_CREDITS
    402
    Not enough credits to execute
    BILLING_FAILED
    402
    Credit deduction failed post-execution; result withheld
    INSUFFICIENT_PERMISSIONS
    403
    API key lacks the required scope
    MODEL_ACCESS_DENIED
    403
    Requested model not available on your tier
    RESOURCE_NOT_FOUND
    404
    Workflow/composition/skill/persona not found
    ACCESS_DENIED
    403
    You don't own this resource
    VALIDATION_ERROR
    400
    Malformed or missing request body fields
    AI_SERVICE_ERROR
    500
    Upstream AI provider failed

    Backoff Guidance

    Error TypeRecommended Action
    429 Rate LimitedWait Retry-After seconds, then retry (max 3 attempts)
    402 Insufficient CreditsDo not retry. Purchase more credits or wait for monthly reset.
    402 Billing FailedDo not retry immediately. Check credit balance, then retry once.
    500 AI Service ErrorRetry with exponential backoff (1s, 2s, 4s). Max 3 attempts.
    401 Auth ErrorsDo not retry. Check API key validity.