Two keys, two patterns: a publishable key for browser writes, a secret key for server reads. Here's the fast path in a Next.js App Router project.
Client component: capture
Use the publishable key (ck_pub_…), safe to ship to the browser:
'use client'
export function Signup() {
async function onSubmit(email) {
await fetch('https://contactapi.dev/v1/contacts', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.NEXT_PUBLIC_CONTACTAPI_PUB_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
})
}
// ...render a form that calls onSubmit
}
Server component: list
Keep the secret key server-side (process.env.CONTACTAPI_SECRET_KEY):
export default async function Contacts() {
const res = await fetch('https://contactapi.dev/v1/contacts?page=1&page_size=20', {
headers: { Authorization: 'Bearer ' + process.env.CONTACTAPI_SECRET_KEY },
cache: 'no-store',
})
const { data } = await res.json()
return {data.map(c => - {c.email}
)}
}
That's contact capture and a contact list, with the secret key never leaving the server. Add PATCH and DELETE the same way when you need them.
Where ContactAPI fits
ContactAPI is an open-source REST API for saving and managing contacts. You POST an email plus any JSON fields, get simple CRUD back, and can reach the same data from an AI client over its MCP server. No CRM, no pipeline stages, no lock-in.