PerspectPerspectDocs
Go to Admin
View as Markdown

Contact Form API

The PerspectAPI v2 SDK provides a contact form system with rate limiting, email notifications, and admin management.

Quick Start

import { PerspectApiV2Client } from 'perspectapi-ts-sdk';

const client = new PerspectApiV2Client({
  baseUrl: 'https://api.example.com',
  apiKey: 'your-api-key'
});

// Submit a contact form
const submission = await client.contacts.submit('my-site', {
  name: 'John Doe',
  email: 'john@example.com',
  subject: 'Product Inquiry',
  message: 'I would like to know more about your products.'
});

console.log(submission.id);     // "ctc_42"
console.log(submission.object); // "contact_submission"
console.log(submission.status); // "pending"

API Reference

contacts.submit(siteName, data)

Submit a contact form to a specific site. Returns the created submission.

const submission = await client.contacts.submit('my-site', {
  email: 'jane@company.com',        // Required
  message: 'Partnership inquiry',    // Required
  name: 'Jane Smith',               // Optional
  first_name: 'Jane',               // Optional (alternative to name)
  last_name: 'Smith',               // Optional
  subject: 'Partnership',           // Optional
  phone: '+1-555-0123',             // Optional
  company: 'Acme Corp',             // Optional
  metadata: {                       // Optional: custom fields
    source: 'homepage-footer',
    campaign: 'summer-2025'
  }
});

Response:

{
  "object": "contact_submission",
  "id": "ctc_42",
  "name": "Jane Smith",
  "first_name": "Jane",
  "last_name": "Smith",
  "email": "jane@company.com",
  "subject": "Partnership",
  "message": "Partnership inquiry",
  "phone": "+1-555-0123",
  "company": "Acme Corp",
  "status": "pending",
  "metadata": { "source": "homepage-footer", "campaign": "summer-2025" },
  "created_at": "2025-04-01T12:00:00Z",
  "processed_at": null
}

contacts.list(siteName, params?)

List contact submissions (admin). Returns a paginated list.

const submissions = await client.contacts.list('my-site', {
  limit: 20,
  status: 'pending'
});

// Response shape:
// {
//   object: "list",
//   data: [{ object: "contact_submission", id: "ctc_42", ... }],
//   has_more: true,
//   url: "/v2/sites/my-site/contacts"
// }

for (const sub of submissions.data) {
  console.log(`${sub.id}: ${sub.email} - ${sub.subject}`);
}

contacts.get(siteName, id)

Get a single contact submission by ID.

const submission = await client.contacts.get('my-site', 'ctc_42');
console.log(submission.message);

Rate Limiting

The API enforces rate limiting: 5 submissions per 15 minutes per IP/email combination. When exceeded, the API returns a rate_limit_error:

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Please try again later."
  }
}

Error Handling

import { PerspectV2Error } from 'perspectapi-ts-sdk';

try {
  await client.contacts.submit('my-site', data);
} catch (err) {
  if (err instanceof PerspectV2Error) {
    switch (err.type) {
      case 'invalid_request_error':
        console.error('Validation failed:', err.message);
        break;
      case 'rate_limit_error':
        console.error('Rate limited, retry later');
        break;
      case 'not_found_error':
        console.error('Site not found');
        break;
      default:
        console.error('API error:', err.message);
    }
  }
}

Framework Examples

React Form

import { PerspectApiV2Client, PerspectV2Error } from 'perspectapi-ts-sdk';

const client = new PerspectApiV2Client({
  baseUrl: import.meta.env.VITE_API_URL,
  apiKey: import.meta.env.VITE_API_KEY
});

function ContactForm() {
  const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus('sending');

    const form = new FormData(e.currentTarget);

    try {
      await client.contacts.submit('my-site', {
        name: form.get('name') as string,
        email: form.get('email') as string,
        message: form.get('message') as string,
      });
      setStatus('sent');
    } catch (err) {
      setStatus('error');
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit" disabled={status === 'sending'}>
        {status === 'sending' ? 'Sending...' : 'Send'}
      </button>
    </form>
  );
}

Remix Action

import { PerspectApiV2Client } from 'perspectapi-ts-sdk';

export async function action({ request }: ActionFunctionArgs) {
  const client = new PerspectApiV2Client({
    baseUrl: process.env.API_URL!,
    apiKey: process.env.API_KEY!
  });

  const form = await request.formData();

  const submission = await client.contacts.submit('my-site', {
    name: form.get('name') as string,
    email: form.get('email') as string,
    subject: form.get('subject') as string,
    message: form.get('message') as string,
  });

  return { success: true, id: submission.id };
}