Skip to main content
This example shows how to build a plugin with tools that use credentials for authentication, such as API keys or OAuth tokens.

Overview

You’ll learn how to:
  • Define credential schemas with addCredential()
  • Implement credential authentication
  • Access credentials in tool invocations
  • Handle authenticated API requests

Complete Example

index.ts
import { createPlugin } from "@choiceopen/atomemo-plugin-sdk-js";
import { z } from "zod";

const plugin = await createPlugin({
  name: "github-toolkit",
  display_name: { en_US: "GitHub Toolkit" },
  description: { en_US: "Tools for interacting with GitHub API" },
  icon: "🐙",
  locales: ["en_US"],
});

// Define GitHub API credential
plugin.addCredential({
  name: "github_api_key",
  display_name: { en_US: "GitHub API Key" },
  description: { en_US: "Personal access token for GitHub API" },
  icon: "🔑",
  parameters: [
    {
      name: "api_token",
      display_name: { en_US: "API Token" },
      description: { en_US: "Your GitHub personal access token" },
      type: "string",
      required: true,
      secret: true,
    },
  ],
  authenticate: async ({ args }) => {
    const { api_token } = args.credential;

    // Validate the token by making a test API call
    const response = await fetch("https://api.github.com/user", {
      headers: {
        Authorization: `Bearer ${api_token}`,
        Accept: "application/vnd.github+json",
      },
    });

    if (!response.ok) {
      throw new Error("Invalid GitHub API token");
    }

    const user = await response.json();

    return {
      authenticated: true,
      username: user.login,
      user_id: user.id,
    };
  },
});

// Define a tool that uses the credential
plugin.addTool({
  name: "list_repositories",
  display_name: { en_US: "List Repositories" },
  description: { en_US: "List GitHub repositories for the authenticated user" },
  icon: "📚",
  credentials: ["github_api_key"],
  parameters: [
    {
      name: "visibility",
      display_name: { en_US: "Visibility" },
      description: { en_US: "Filter repositories by visibility" },
      type: "string",
      required: false,
      enum: ["all", "public", "private"],
      default: "all",
    },
    {
      name: "sort",
      display_name: { en_US: "Sort By" },
      description: { en_US: "Sort repositories by this field" },
      type: "string",
      required: false,
      enum: ["created", "updated", "pushed", "full_name"],
      default: "updated",
    },
    {
      name: "limit",
      display_name: { en_US: "Limit" },
      description: { en_US: "Maximum number of repositories to return" },
      type: "number",
      required: false,
      default: 10,
    },
  ],
  invoke: async ({ args }) => {
    const { visibility, sort, limit } = args.parameters;
    const credentials = args.credentials?.github_api_key;

    if (!credentials?.api_token) {
      throw new Error("GitHub API token is required");
    }

    // Build API URL with query parameters
    const params = new URLSearchParams({
      visibility: visibility || "all",
      sort: sort || "updated",
      per_page: String(limit || 10),
    });

    const response = await fetch(
      `https://api.github.com/user/repos?${params}`,
      {
        headers: {
          Authorization: `Bearer ${credentials.api_token}`,
          Accept: "application/vnd.github+json",
        },
      }
    );

    if (!response.ok) {
      throw new Error(`GitHub API error: ${response.statusText}`);
    }

    const repositories = await response.json();

    return {
      count: repositories.length,
      repositories: repositories.map((repo: any) => ({
        name: repo.name,
        full_name: repo.full_name,
        description: repo.description,
        url: repo.html_url,
        stars: repo.stargazers_count,
        private: repo.private,
        updated_at: repo.updated_at,
      })),
    };
  },
});

// Add another tool that creates an issue
plugin.addTool({
  name: "create_issue",
  display_name: { en_US: "Create Issue" },
  description: { en_US: "Create a new issue in a GitHub repository" },
  icon: "🐛",
  credentials: ["github_api_key"],
  parameters: [
    {
      name: "owner",
      display_name: { en_US: "Repository Owner" },
      description: { en_US: "The owner of the repository" },
      type: "string",
      required: true,
    },
    {
      name: "repo",
      display_name: { en_US: "Repository Name" },
      description: { en_US: "The name of the repository" },
      type: "string",
      required: true,
    },
    {
      name: "title",
      display_name: { en_US: "Issue Title" },
      description: { en_US: "The title of the issue" },
      type: "string",
      required: true,
    },
    {
      name: "body",
      display_name: { en_US: "Issue Body" },
      description: { en_US: "The body content of the issue" },
      type: "string",
      required: false,
    },
    {
      name: "labels",
      display_name: { en_US: "Labels" },
      description: { en_US: "Comma-separated list of labels" },
      type: "string",
      required: false,
    },
  ],
  invoke: async ({ args }) => {
    const { owner, repo, title, body, labels } = args.parameters;
    const credentials = args.credentials?.github_api_key;

    if (!credentials?.api_token) {
      throw new Error("GitHub API token is required");
    }

    const issueData: any = { title, body };
    if (labels) {
      issueData.labels = labels.split(",").map((l: string) => l.trim());
    }

    const response = await fetch(
      `https://api.github.com/repos/${owner}/${repo}/issues`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${credentials.api_token}`,
          Accept: "application/vnd.github+json",
          "Content-Type": "application/json",
        },
        body: JSON.stringify(issueData),
      }
    );

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Failed to create issue: ${error.message}`);
    }

    const issue = await response.json();

    return {
      success: true,
      issue_number: issue.number,
      issue_url: issue.html_url,
      state: issue.state,
    };
  },
});

await plugin.run();

Code Breakdown

Defining Credentials

Credentials are defined with parameters and an optional authenticate function:
plugin.addCredential({
  name: "github_api_key",
  parameters: [
    {
      name: "api_token",
      type: "string",
      required: true,
      secret: true,  // Marks field as sensitive
    },
  ],
  authenticate: async ({ args }) => {
    // Validate credentials
    // Return authentication metadata
  },
});
The secret: true flag ensures the parameter is masked in the UI and stored securely.

Authentication Function

The authenticate function validates credentials and returns metadata:
authenticate: async ({ args }) => {
  const { api_token } = args.credential;
  
  // Test the credential
  const response = await fetch("https://api.github.com/user", {
    headers: { Authorization: `Bearer ${api_token}` },
  });
  
  if (!response.ok) {
    throw new Error("Invalid token");
  }
  
  const user = await response.json();
  
  // Return metadata (stored and accessible in tools)
  return {
    authenticated: true,
    username: user.login,
    user_id: user.id,
  };
}

Using Credentials in Tools

Tools that require credentials:
  1. List them in the credentials array
  2. Access them via args.credentials
plugin.addTool({
  name: "list_repositories",
  credentials: ["github_api_key"],  // Required credential
  invoke: async ({ args }) => {
    // Access the credential
    const credentials = args.credentials?.github_api_key;
    const token = credentials?.api_token;
    
    // Use in API calls
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
    });
  },
});

Credential Structure

In the invoke function, credentials are accessed as:
args.credentials = {
  [credential_name]: {
    // Original parameters
    api_token: "ghp_...",
    // Plus authentication metadata
    authenticated: true,
    username: "octocat",
    user_id: 123,
  }
}

Best Practices

Implement the authenticate function to verify credentials before they’re used:
authenticate: async ({ args }) => {
  // Make a test API call to validate
  const response = await fetch("...");
  if (!response.ok) {
    throw new Error("Invalid credentials");
  }
  return { authenticated: true };
}
Always use secret: true for tokens, passwords, and API keys:
{
  name: "api_token",
  type: "string",
  secret: true,  // Masked in UI, encrypted at rest
}
Check for credentials in your tool implementation:
const credentials = args.credentials?.credential_name;
if (!credentials) {
  throw new Error("Credentials are required");
}
Store information that might be useful across tool invocations:
return {
  authenticated: true,
  username: user.login,
  account_type: user.type,
  permissions: user.permissions,
};

Multiple Credentials

Tools can require multiple credentials:
plugin.addTool({
  name: "sync_to_cloud",
  credentials: ["github_api_key", "aws_credentials"],
  invoke: async ({ args }) => {
    const github = args.credentials?.github_api_key;
    const aws = args.credentials?.aws_credentials;
    // Use both credentials
  },
});

Next Steps

Custom Model

Learn how to add custom AI models

Credential Definition

Full credential API reference

Build docs developers (and LLMs) love