// Reference

One API.
SOC + Help Desk.
Fully headless.

Sam is the AI platform that powers Pink Duck — a 24/7 security operations center and autonomous help desk. Use these docs to plug Sam in anywhere.

Multi-tenant
Help desk
Cybersecurity
Base URL https://9vy0x0o0cc.execute-api.us-east-2.amazonaws.com
01
Register

Create your account at sam.pinkduckcompany.com — dashboard only, no API needed.

02
Authenticate

Exchange credentials for a Cognito ID token and pass it as a Bearer on every request.

03
Call

Every endpoint is live immediately. UI and API share the same data. Multi-tenancy is baked in.

Authentication

Authenticate with the API

Sam uses AWS Cognito for authentication. Exchange your credentials for an ID token, then pass it as a Bearer value in the Authorization header on every request. Tokens expire after one hour.

authenticate.js JavaScript
async function exchange(userPoolId, clientId, email, password) {
  const region = userPoolId.split('_')[0];
  const resp = await fetch(`https://cognito-idp.${region}.amazonaws.com/`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-amz-json-1.1',
      'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth',
    },
    body: JSON.stringify({
      AuthFlow: 'USER_PASSWORD_AUTH',
      ClientId: clientId,
      AuthParameters: { USERNAME: email, PASSWORD: password },
    }),
  });
  const data = await resp.json();
  if (!resp.ok) throw new Error(data.message);
  return data.AuthenticationResult.IdToken;
}

const token = await exchange(
  'us-east-2_J5UAtrmqZ',       // User Pool ID
  'l988ndjcmbcfim10t25qehh1p',  // App Client ID
  'you@yourcompany.com',
  'your-password'
);

// Attach to every request
const headers = {
  'Authorization': `Bearer ${token}`,
  'Content-Type': 'application/json',
};
Tickets

List tickets

Returns tickets in your account. Filter by status or date. For MSP-wide queries across all client accounts, use /msp/ticket instead.

GET
/ticket?status=O
Returns tickets, filterable by status code or month.
GET
/msp/ticket?status=O
MSP-wide: returns tickets across all client accounts.
CodeLabelDescription
OOpenActively being worked. Default help desk queue view.
OEScheduledScheduled for a later date.
OBBlockedWaiting on the customer before work can continue.
ORNeeds reviewSam's analysis requires human sign-off.
CResolvedConfirmed resolved and archived.
CINo actionNothing actionable to complete.
CRAccepted riskCustomer accepted the risk on this ticket.
CSAuto-closedSam closed with high confidence.
list-tickets.js JavaScript
const BASE = 'https://9vy0x0o0cc.execute-api.us-east-2.amazonaws.com';

// Open tickets in current account
const open = await fetch(`${BASE}/ticket?status=O`, { headers })
  .then(r => r.json());

// Tickets updated in a given month
const month = await fetch(`${BASE}/ticket?date=2026-01`, { headers })
  .then(r => r.json());

// MSP-wide: open across all client accounts
const mspOpen = await fetch(`${BASE}/msp/ticket?status=O`, { headers })
  .then(r => r.json());
Tickets

Get a ticket

Fetch a single ticket by ID, including its full update history. Tickets carry a service tag: 1 Security, 2 Help Desk, 3 Business Risk, 4 Project.

GET
/ticket/{id}
Returns a ticket and its complete touch history.
get-ticket.js JavaScript
const ticket = await fetch(`${BASE}/ticket/TKT-00147`, { headers })
  .then(r => r.json());

// ticket.touches — threaded updates in order
// ticket.status  — single character status code
// ticket.service — 1=Security 2=HelpDesk 3=BizRisk 4=Project
Tickets

Create a ticket

Creating a ticket is two steps. First POST /ticket to create the record, then POST /ticket/{id} with the returned ID to add an opening touch — a threaded message, like the first entry in a conversation thread.

POST
/ticket
Creates a new ticket. Returns the object including its ID.
POST
/ticket/{id}
Adds a touch (threaded update) to an existing ticket.
create-ticket.js JavaScript
// Step 1: Create the ticket
const ticket = await fetch(`${BASE}/ticket`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    name:    'Printer offline — east wing 3rd floor',
    source:  'api',   // 'google' | 'microsoft' | 'api'
    service: 2,         // 1=Security 2=HelpDesk 3=BizRisk 4=Project
  }),
}).then(r => r.json());

// Step 2: Add the opening touch
await fetch(`${BASE}/ticket/${ticket.id}`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    value: 'HP LaserJet not responding. Tried power cycling — no change.',
    user:  'jane@acmecorp.com',
  }),
}).then(r => r.json());
Tickets

Update a ticket

Patch any field on a ticket — most commonly status to move it through the workflow. Only fields you include are changed.

PATCH
/ticket/{id}
Partial update. Send only the fields to change.
DEL
/ticket/{id}
Permanently deletes. Prefer status X for auditability.
POST
/ticket/{into}/merge/{from}
Merges from into into, consolidating touches.
update-ticket.js JavaScript
// Close a ticket
await fetch(`${BASE}/ticket/TKT-00147`, {
  method: 'PATCH',
  headers,
  body: JSON.stringify({ status: 'C' }),
}).then(r => r.json());

// Change due date
await fetch(`${BASE}/ticket/TKT-00147`, {
  method: 'PATCH',
  headers,
  body: JSON.stringify({ due: '2026-11-15'}),
}).then(r => r.json());

// Merge duplicate tickets
await fetch(`${BASE}/ticket/TKT-00147/merge/TKT-00151`, {
  method: 'POST', headers,
}); // TKT-00151 merged into TKT-00147
Integrations

List monitors

Monitors are integrations Sam connects to on your behalf. Once connected, Sam continuously scans for compliance issues and security incidents.

microsoft
M365 — users, mailboxes, MFA, conditional access, SharePoint
google
Workspace — users, Drive, Gmail security, 2-step verification
cloudflare
DNS records, audit logs, zone security posture
level
RMM — endpoint patch status, device health
GET
/integration
Returns all connected monitors for the current account.
list-monitors.js JavaScript
const monitors = await fetch(`${BASE}/integration`, { headers })
  .then(r => r.json());

// [ { name: 'microsoft' }, { name: 'google' }, ... ]
Integrations

Connect a monitor

Connect a new monitor by POSTing credentials to /integration/{name}. The body is the credential object for that service. Sam validates and begins scanning immediately on success.

See the full list of available integrations in the Monitors section of the Sam interface.

POST
/integration/{name}
Connects a monitor. Body is the credentials object for that service.
connect-monitor.js JavaScript
// Google Workspace — service account JSON key
await fetch(`${BASE}/integration/google`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    type: 'service_account',
    project_id: 'your-project-id',
    private_key_id: '...',
    private_key: '-----BEGIN RSA PRIVATE KEY-----\n...',
    client_email: 'sam@your-project.iam.gserviceaccount.com',
  }),
}).then(r => r.json());

// Microsoft 365 — app registration credentials
await fetch(`${BASE}/integration/microsoft`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    tenant_id:     'your-tenant-id',
    client_id:     'your-app-client-id',
    client_secret: 'your-app-secret',
  }),
}).then(r => r.json());
Integrations

Disconnect a monitor

Send DELETE to /integration/{key}. Sam immediately stops scanning and removes stored credentials. Existing tickets created by the monitor are preserved.

DEL
/integration/{key}
Disconnects the monitor and removes credentials. Tickets are retained.
disconnect-monitor.js JavaScript
await fetch(`${BASE}/integration/google`, {
  method: 'DELETE',
  headers,
});
Multi-tenancy

Account delegation

By default every API call operates in your root MSP account. To act on behalf of a client, pass the account header set to their domain. Sam scopes all reads and writes to that account for any request carrying the header.

No separate login, token exchange, or session — just the header. Your root credentials authorize you for every account your MSP manages.

The account header works on every endpoint — tickets, integrations, team, files, and audit. Drop it to fall back to your MSP root context.
GET
/msp/account
Returns all client accounts under your MSP organization.
delegation.js JavaScript
// Act on behalf of a client account
const clientHeaders = { ...headers, 'account': 'acmecorp.com' };

// Open tickets in the client account
const tickets = await fetch(`${BASE}/ticket?status=O`, { headers: clientHeaders })
  .then(r => r.json());

// Loop all clients, print open ticket counts
const accounts = await fetch(`${BASE}/msp/account`, { headers })
  .then(r => r.json());

for (const acct of accounts) {
  const open = await fetch(`${BASE}/ticket?status=O`, {
    headers: { ...headers, account: acct.domain },
  }).then(r => r.json());
  console.log(acct.domain, open.length, 'open');
}
Multi-tenancy

Invite a customer

Invite a customer to your managed services. They receive an email to accept, after which you can delegate into their account.

POST
/msp/invitation
Sends an invitation email. Recipient registers via the Sam dashboard.
POST
/msp/account/{group}/{user}
Assigns an admin to a client account group.
DEL
/msp/account/{group}/{user}
Removes an admin from a client account group.
invite-customer.js JavaScript
// Send invitation
await fetch(`${BASE}/msp/invitation`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ to: 'customer@acmecorp.com' }),
});

// Assign a team member to manage the account
await fetch(`${BASE}/msp/account/acmecorp.com/engineer@pinkduckcompany.com`, {
  method: 'POST', headers,
});

// Remove them from the account
await fetch(`${BASE}/msp/account/acmecorp.com/engineer@pinkduckcompany.com`, {
  method: 'DELETE', headers,
});
Want totry Sam?

Send an email and Sam will create your first help desk ticket.

Ask Sam anything
help@sam.pinkduckcompany.com