Platforms
Connect a Google account to read and send Gmail, manage Google Calendar events, work with Drive files and sync Google Contacts.
A single Google connection gives Pozy access to one person's Gmail, Google Calendar, Google Drive and Google Contacts, as one account per mailbox. At connect time you decide which of those four areas to ask for. Ask for more later and the account id stays the same. The calls themselves are the same for Google and Microsoft mailboxes: see Email, Calendar and Files.
#What you can do
| Area | Value in scopes |
Google permission | What it unlocks |
|---|---|---|---|
email |
gmail.modify |
List, search, read, send, reply, label, archive and trash Gmail | |
| Calendar | calendar |
calendar |
Events with Google Meet links, RSVP, free-busy and availability |
| Files | files |
drive.file |
Only files created through Pozy or picked by the user |
| Contacts | contacts |
contacts.readonly |
Google Contacts copied into /v1/contacts once a day, read-only |
On top of those, each connection requests openid, userinfo.email and userinfo.profile, which tell Pozy whose mailbox it is.
| Property | Value |
|---|---|
| Platform id | google |
| Accounts | One per Google account and profile. Reconnecting widens the same account |
| Inbox | New mail and API sends appear as inbox conversations, one per Gmail thread |
| Posting and analytics | No |
| List the accounts | GET /v1/accounts?platform=google |
#Connect an account
scopes is required. Ask only for what your product uses: Google lists every permission on the consent screen, and Workspace admins review apps by what they request.
curl "https://api.pozy.ai/v1/connect/google?profileId=6710c3a9e4b0d2f1a8c95e21&scopes=email,calendar&redirect_url=https://app.example.com/connected" \
-H "Authorization: Bearer $POZY_API_KEY"const res = await fetch("https://api.pozy.ai/v1/connect/google?profileId=6710c3a9e4b0d2f1a8c95e21&scopes=email,calendar&redirect_url=https:%2F%2Fapp.example.com%2Fconnected", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// Redirect the user to data.authUrlimport os, requests
resp = requests.get(
"https://api.pozy.ai/v1/connect/google",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={
"profileId": "6710c3a9e4b0d2f1a8c95e21",
"scopes": "email,calendar",
"redirect_url": "https://app.example.com/connected",
},
)
auth_url = resp.json()["authUrl"]The user picks a Google account and approves. They return to your redirect_url with connected=google, profileId, accountId and username (the mailbox address). From the API there is no Pozy screen in between: your app picks the areas and the user sees only Google.
What counts is what Google grants, not what you asked for. A permission the user unchecks on Google's screen leaves its area out, and calls in that area answer 412 account_scope_missing. If the user unchecks all of them, Pozy saves no account and sends your redirect error=no_access_granted.
#Add areas later
Connect the same Google account again with the longer list. Google keeps what it granted before and Pozy updates the same account. Pass reconnectAccountId to make sure the reconnect lands on that account, and loginHint to preselect the mailbox:
curl "https://api.pozy.ai/v1/connect/google?profileId=6710c3a9e4b0d2f1a8c95e21&scopes=email,calendar,files&reconnectAccountId=6710c501e4b0d2f1a8c95e77&loginHint=ana@example.com" \
-H "Authorization: Bearer $POZY_API_KEY"A reconnect can add areas but never takes one away, even if you leave it out. To remove an area, disconnect the account, then connect it again asking only for the areas you still need.
#Gmail limits
| What | Limit |
|---|---|
| Daily sends from a personal Gmail mailbox | 200 |
| Daily sends from a Google Workspace mailbox | 500 |
| When the daily count restarts | 00:00 UTC |
| One body sent to over 50 recipients in an hour | A pause of 10 minutes, twice as long on each repeat up to 24 hours, with an account.rate_limited event |
| Addresses per field | 100 in to, 100 in cc, 100 in bcc |
| Attachments | 20 files, 25 MB combined |
A reply uses up a send, and so does a message Gmail refuses. Read your remaining budget from the X-Pozy-Send-Limit, X-Pozy-Send-Remaining and X-Pozy-Send-Reset headers on each send, or from sendCap in GET /v1/accounts/{accountId}/health. Use the mailbox for personal mail; newsletters belong elsewhere.
#Drive access
Pozy connects Drive with drive.file, never full Drive access. It sees two kinds of files: files created through Pozy (POST /v1/files, POST /v1/files/folders) and files the user picked in Google Picker. Everything else stays invisible and returns 404 with details.reason: "file_not_granted". Picker sessions, which let the user grant an existing file, are not available yet, so for now Pozy works with the files it created. Once they open, a pick is a lasting grant until the user removes the app from their Google account.
Google's own formats are not stored as files, so Pozy exports them on download: Docs and Slides become PDF, Sheets become XLSX and Drawings become PNG. Forms and Sites cannot be downloaded at all and say downloadable: false. Going the other way, convertTo on an upload turns the file into a Google format.
#Contacts sync
With the contacts area, Pozy copies Google Contacts into the profile's contacts right after connecting, then once a day for changes only. There is no on-demand trigger. List them with GET /v1/contacts?profileId=...&platform=google.
- People without an email address are skipped. Only the first address is used.
- A person already known from another channel (WhatsApp, SMS...) with the same email gets a Google email channel instead of a second contact.
- The sync writes name, company, photo and email, and never touches tags, custom fields or other channels.
- Nothing is written back to Google. Deleting a contact in Google removes its Google channel and fires
contact.deleted; the contact itself stays.
#Push notifications
When the account connects, Pozy subscribes to Google's push notifications for the granted areas and keeps them renewed (Google channels expire within seven days; Pozy renews any that expire within 24 hours). You only subscribe your own webhook to the events you want.
| Event | Area | Fires when |
|---|---|---|
message.received, message.sent, conversation.started |
email |
Mail arrives in the inbox, is sent through the API, or starts a new thread |
calendar.event.created, .updated, .deleted |
calendar |
Someone adds, edits or removes an event in the primary calendar |
file.created, file.updated, file.deleted |
files |
A Drive file Pozy has access to is added, edited or removed |
file.picked |
files |
The user is done choosing files in the picker |
contact.updated, contact.deleted |
contacts |
The daily sync changes a contact (up to a day after the change) |
account.rate_limited |
email |
The mailbox enters a sending cooldown |
The push field of GET /v1/accounts/{accountId}/health is active; pending, which Pozy retries within six hours; failed, after three failed renewals, retried once a day; or not_applicable. Expect Google to repeat a change now and then, and deduplicate on the object's id. When Gmail has lost the history between two notifications, Pozy picks up from the present, so fetch anything you missed with GET /v1/email/messages.
#Errors
| Error | Cause | Fix |
|---|---|---|
400 invalid_field_value, param: scopes |
scopes missing, empty or unknown |
Pass a subset of email, calendar, files, contacts |
error=oauth_denied on the redirect |
The user said no, or Google turned the app down. With admin_policy_enforced, a Workspace admin has blocked it |
Try the connect again, or have the admin allow the app |
401 reconnect_required |
Google no longer accepts the account's access | Have the user reconnect |
412 account_scope_missing |
The account lacks the area. details has grantedAreas and missingArea |
Reconnect with the area added |
429 email_send_cap_reached |
Today's cap is used. details.cap has limit, used, resetsAt |
Wait for resetsAt |
429 rate_limited |
A sending cooldown, or Google throttled the mailbox | Wait for Retry-After |
502 platform_api_error |
Google returned an unmapped error | Retry. The message carries Google's |
If Google will not renew access, for instance because the user removed the app in their Google account settings, Pozy marks the account disconnected and sends account.disconnected.
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/connect/{platform} |
Get OAuth connect URL |
GET /v1/accounts/{accountId}/health |
Check account health |