Ir al contenido

API Integration Tutorial

Esta página aún no está disponible en tu idioma.

This tutorial walks you through building a service integration that creates and launches campaigns via the Pidgr API. You’ll use the open source pidgr-proto package, which provides type-safe gRPC client stubs for Go and TypeScript.

  • A Pidgr organization with admin access
  • An API key (created in the admin dashboard under SettingsAPI Keys)
  • Go 1.21+ or Node.js 18+

Pidgr enforces passkey authentication for all interactive users — both on the mobile app and the admin dashboard. Users cannot operate with email OTP alone; they must register a passkey (WebAuthn/FIDO2) during their first sign-in.

API keys are exempt from passkey enforcement. When you authenticate with an API key (pidgr_k_ prefix), the request bypasses the passkey check entirely. This is by design — service integrations, CI/CD pipelines, and MCP agents don’t have biometric capabilities.

Auth methodPasskey requiredUse case
JWT (interactive user)YesMobile app, admin dashboard
API keyNoService integrations, MCP, CI/CD
SSO/SAMLYes (after federation)Enterprise users
Terminal window
mkdir pidgr-integration && cd pidgr-integration
go mod init example.com/pidgr-integration
Terminal window
go get github.com/pidgr/pidgr-proto/gen/go@latest
go get google.golang.org/grpc
package main
import (
"context"
"crypto/tls"
"fmt"
"log"
pidgrv1 "github.com/pidgr/pidgr-proto/gen/go/pidgr/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
)
func main() {
// Connect to the Pidgr API
conn, err := grpc.NewClient("api.pidgr.com:443",
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
)
if err != nil {
log.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
// Attach the API key to all requests
apiKey := "pidgr_k_your_key_here" // use an environment variable in production
ctx := metadata.AppendToOutgoingContext(context.Background(),
"authorization", "Bearer "+apiKey,
)
// Create service clients
templateClient := pidgrv1.NewTemplateServiceClient(conn)
campaignClient := pidgrv1.NewCampaignServiceClient(conn)
// 1. Create a template
tmpl, err := templateClient.CreateTemplate(ctx, &pidgrv1.CreateTemplateRequest{
Name: "weekly-update",
Title: "Weekly Engineering Update",
Body: "Hi {{name}},\n\nHere's your weekly update:\n\n{{content}}",
Variables: []*pidgrv1.TemplateVariable{
{Name: "name", Source: pidgrv1.TEMPLATE_VARIABLE_SOURCE_PROFILE, Required: true},
{Name: "content", Source: pidgrv1.TEMPLATE_VARIABLE_SOURCE_CUSTOM, Required: true},
},
})
if err != nil {
log.Fatalf("failed to create template: %v", err)
}
fmt.Printf("Created template: %s (version %d)\n", tmpl.Template.Id, tmpl.Template.Version)
// 2. Create a campaign using the template
campaign, err := campaignClient.CreateCampaign(ctx, &pidgrv1.CreateCampaignRequest{
Name: "Weekly Update - Week 12",
Title: "Weekly Engineering Update",
TemplateId: tmpl.Template.Id,
TemplateVersion: tmpl.Template.Version,
SenderName: "Engineering",
Audience: []*pidgrv1.AudienceMember{
{UserId: "user-uuid-1", Variables: map[string]string{"content": "Sprint review on Friday."}},
{UserId: "user-uuid-2", Variables: map[string]string{"content": "Sprint review on Friday."}},
},
})
if err != nil {
log.Fatalf("failed to create campaign: %v", err)
}
fmt.Printf("Created campaign: %s\n", campaign.Campaign.Id)
// 3. Launch the campaign
started, err := campaignClient.StartCampaign(ctx, &pidgrv1.StartCampaignRequest{
CampaignId: campaign.Campaign.Id,
})
if err != nil {
log.Fatalf("failed to start campaign: %v", err)
}
fmt.Printf("Campaign started: %s (status: %s)\n",
started.Campaign.Id, started.Campaign.Status)
// 4. Monitor deliveries
deliveries, err := campaignClient.ListDeliveries(ctx, &pidgrv1.ListDeliveriesRequest{
CampaignId: campaign.Campaign.Id,
})
if err != nil {
log.Fatalf("failed to list deliveries: %v", err)
}
for _, d := range deliveries.Deliveries {
fmt.Printf(" %s: %s\n", d.RecipientEmail, d.Status)
}
}
Terminal window
mkdir pidgr-integration && cd pidgr-integration
npm init -y
npm install typescript tsx @types/node --save-dev

The @pidgr/proto package is published on npm as a public package.

Terminal window
npm install @pidgr/proto @connectrpc/connect @connectrpc/connect-web @bufbuild/protobuf
import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web";
import { CampaignService } from "@pidgr/proto/pidgr/v1/campaign_pb";
import { TemplateService } from "@pidgr/proto/pidgr/v1/template_pb";
import {
TemplateVariableSourceSchema,
} from "@pidgr/proto/pidgr/v1/common_pb";
// Create a transport with API key authentication
const transport = createConnectTransport({
baseUrl: "https://api.pidgr.com",
interceptors: [
(next) => async (req) => {
const apiKey = process.env.PIDGR_API_KEY; // never hardcode keys
req.header.set("authorization", `Bearer ${apiKey}`);
return next(req);
},
],
});
// Create service clients
const templateClient = createClient(TemplateService, transport);
const campaignClient = createClient(CampaignService, transport);
async function main() {
// 1. Create a template
const tmpl = await templateClient.createTemplate({
name: "weekly-update",
title: "Weekly Engineering Update",
body: "Hi {{name}},\n\nHere's your weekly update:\n\n{{content}}",
variables: [
{ name: "name", source: 1 /* PROFILE */, required: true },
{ name: "content", source: 2 /* CUSTOM */, required: true },
],
});
console.log(`Created template: ${tmpl.template!.id} (version ${tmpl.template!.version})`);
// 2. Create a campaign using the template
const campaign = await campaignClient.createCampaign({
name: "Weekly Update - Week 12",
title: "Weekly Engineering Update",
templateId: tmpl.template!.id,
templateVersion: tmpl.template!.version,
senderName: "Engineering",
audience: [
{ userId: "user-uuid-1", variables: { content: "Sprint review on Friday." } },
{ userId: "user-uuid-2", variables: { content: "Sprint review on Friday." } },
],
});
console.log(`Created campaign: ${campaign.campaign!.id}`);
// 3. Launch the campaign
const started = await campaignClient.startCampaign({
campaignId: campaign.campaign!.id,
});
console.log(`Campaign started: ${started.campaign!.id} (status: ${started.campaign!.status})`);
// 4. Monitor deliveries
const deliveries = await campaignClient.listDeliveries({
campaignId: campaign.campaign!.id,
});
for (const d of deliveries.deliveries) {
console.log(` ${d.recipientEmail}: ${d.status}`);
}
}
main().catch(console.error);
Terminal window
PIDGR_API_KEY=pidgr_k_your_key npx tsx main.ts

The pidgr-proto repository is open source (Apache 2.0) and contains the canonical Protocol Buffer definitions for all Pidgr services.

LanguagePackageInstall
Gogithub.com/pidgr/pidgr-proto/gen/gogo get github.com/pidgr/pidgr-proto/gen/go@latest
TypeScript@pidgr/protonpm install @pidgr/proto
RustGit dependencypidgr-proto = { git = "...", tag = "v0.38.1" }

If you need custom generation (e.g., for a language not listed above), clone the proto definitions and run buf generate with your own plugins:

Terminal window
git clone https://github.com/pidgr/pidgr-proto.git
cd pidgr-proto
# Install buf (https://buf.build/docs/installation)
# Modify buf.gen.yaml for your target language
buf generate

The API returns standard gRPC status codes. Handle errors appropriately in your integration:

import "google.golang.org/grpc/status"
resp, err := campaignClient.StartCampaign(ctx, req)
if err != nil {
st, ok := status.FromError(err)
if ok {
switch st.Code() {
case codes.NotFound:
log.Printf("campaign not found: %s", st.Message())
case codes.PermissionDenied:
log.Printf("missing permission: %s", st.Message())
case codes.InvalidArgument:
log.Printf("invalid request: %s", st.Message())
default:
log.Printf("API error [%s]: %s", st.Code(), st.Message())
}
}
}
import { ConnectError, Code } from "@connectrpc/connect";
try {
await campaignClient.startCampaign({ campaignId: "..." });
} catch (err) {
if (err instanceof ConnectError) {
switch (err.code) {
case Code.NotFound:
console.error("Campaign not found:", err.message);
break;
case Code.PermissionDenied:
console.error("Missing permission:", err.message);
break;
default:
console.error(`API error [${err.code}]: ${err.message}`);
}
}
}
  • Never hardcode API keys — use environment variables or a secrets manager
  • Scope API keys — grant only the permissions your integration needs
  • Rotate keys — create new keys periodically and revoke old ones
  • Use separate keys per integration — isolate blast radius if a key is compromised
  • Monitor usage — check last_used_at on API keys to detect unused or suspicious activity
  • Explore the full API Reference for all available services
  • Set up Webhooks to receive campaign delivery outcomes
  • Install the MCP Server for AI agent integration