StonePad Integration API
1. Authentication
StonePad uses OAuth 2.0 Authorization Code Grant with PKCE (RFC 6749 § 4.1 + RFC 7636). Bearer access tokens are required for every API call except the OAuth endpoints themselves.Two scopes are defined:
Applications must be registered by StonePad support (support@stonepad.co.uk) to receive client_id, client_secret, and registered redirect_uri values. Client secrets are stored as SHA-256 hashes; the plain value is only shown once at registration.
1.1 Authorize endpoint
GET https://api.stonepad.app/functions/v1/oauth-authorizeQuery parameters:
The user is redirected through StonePad's consent screen and, on approval, sent back to redirect_uri with ?code=<authorization_code>&state=<state>. Authorization codes are single-use and expire in 5 minutes.
Errors are returned as query-string parameters on redirect_uri (?error=<code>&error_description=<text>&state=<state>) per RFC 6749 § 4.1.2.1.
1.2 Token endpoint
POST https://api.stonepad.app/functions/v1/oauth-token
Content-Type: application/x-www-form-urlencodedpapplication/json bodies are also accepted. Client credentials may be sent in the body or via HTTP Basic authentication (RFC 6749 § 2.3.1).
Supported grant types:
Authorization code exchange:
Refresh token rotation (RFC 6749 § 10.4 – rotates on every use)
Success response:
{
"access_token": "…",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "leads:read leads:write"
}- Access tokens last 1 hour (3600 seconds).
- Refresh tokens last 30 days and are rotated on every use — the previous refresh token is revoked as soon as a new pair is issued.
Errors follow RFC 6749 § 5.2 (invalid_request, invalid_client, invalid_grant, unsupported_grant_type, invalid_scope, etc.):
{ "error": "invalid_grant", "error_description": "Authorization code expired" }1.3 Revoke endpoint
POST https://api.stonepad.app/functions/v1/oauth-revoke
Content-Type: application/x-www-form-urlencodedRFC 7009 revocation. Fields:
1.4 Making authenticated requests
Every API call must include:
Authorization: Bearer <access_token>
401 responses indicate the token is missing, invalid, expired, or revoked. 403 responses indicate the token is valid but lacks the required scope.
2. GET /api-me — Verify credentials
Returns the identity behind the current access token. Zapier calls this immediately after the OAuth flow to prove the connection works, and uses the response to build the human-readable connection label.
GET /api-me
Authorization: Bearer <access_token>
Required scope: any (no scope check).
Response 200 OK:
{
"id": "e7c1f0d3-…-…",
"email": "user@example.com",
"name": "Jane Doe",
"company_id": "c9a…",
"company_name": "Example Kitchens Ltd",
"membership_role": "owner",
"scopes": ["leads:read", "leads:write"]
}3. POST /api-leads — Create Lead
Creates a lead on the authenticated user's active company. Powers Zapier's "Create Lead in StonePad" action.
Required scope: leads:write.
POST /api-leads
Authorization: Bearer <access_token>
Content-Type: application/json3.1 Request body
All fields are strings unless otherwise noted. name or email is required; the rest are optional.
{
"name": "Carl Smith",
"email": "carl@example.com",
"phone": "07306100000",
"company": "Kitchen Company",
"source": "Website",
"message": "Quote requested for new granite worktop.",
"budget": "5000",
"status": "new",
"priority": "High",
"due_date": "2026-10-01",
"handed_to": "Jeoff Jones",
"post_code": "SW1A 1AA",
"custom_fields": {
"Finish": "Polished",
"Thickness": "20mm"
},
"attachments": [
"https://api.typeform.com/responses/files/<hash>/kitchen.pdf",
"https://api.typeform.com/responses/files/<hash>/measurements.jpg"
]
}Response 201 Created returns the full stored lead row, including generated id, timestamps, tenant scope (company_id, branch_id), and normalized field values:
{
"id": "1fc1a338-fd66-414c-8924-efb69888000a",
"name": "Carl Smith",
"email": "carl@example.com",
"phone": "07306100000",
"company": "Kitchen Company",
"source": "Website",
"status": "new",
"priority": "High",
"due_date": "2026-10-01",
"handed_to": "Jeoff Jones",
"custom_fields": { "Finish": "Polished", "Thickness": "20mm" },
"attachments": ["https://…/kitchen.pdf", "https://…/measurements.jpg"],
"created_by": "Zapier",
"created_at": "2026-09-15T14:20:41.650Z",
"updated_at": "2026-09-15T14:20:41.650Z"
}3.2 Validation errors (all 400 Bad Request)
- Either name or email is required
- Status "<value>" is not one of the allowed values. Valid options: <list>.
- custom_fields must be a flat object of key -> value pairs
- custom_fields may contain at most 50 keys
- attachments must be absolute http(s) URLs; received "<snippet>"
- attachments may not exceed 20 entries
- Typeform's authenticated file URL is not usable as an attachment.
422 Unprocessable Entity is returned when the authenticated company has no branches configured (a StonePad-side prerequisite before leads can be created).
4. GET /api-leads — List recent leads
Polling endpoint that returns leads most-recent-first, optionally filtered by creation timestamp. Powers Zapier's "New Lead in StonePad" polling trigger.
GET /api-leads?since=<iso8601>&limit=<n>
Authorization: Bearer <access_token>Required scope: leads:read.
Response 200 OK: array of full lead objects (same shape as the POST response), ordered by created_at DESC.
Zapier's recommended polling pattern is to store the highest created_at seen and pass it as since on the next call
5. POST /api-lead-attachments — Upload lead attachment
Uploads a file into the tenant's lead-attachments Storage bucket and returns a stable public URL. Intended for source forms whose file URLs are behind authentication (Typeform, HubSpot, JotForm private forms) and cannot be linked to directly from a lead detail page.
POST /api-lead-attachments
Authorization: Bearer <access_token>Required scope: leads:read.
Two request shapes are supported. Zapier normally uses the JSON form because Zapier's own String-typed inputs cannot reliably send multipart:
5.1 Multipart upload (raw bytes)
Content-Type: multipart/form-data; boundary=…- Form field
file: the file bytes (required, must be non-empty). - Maximum size: 25 MiB per request.
5.2 URL fetch (server-side download)
Content-Type: application/json
{ "url": "https://example.com/path/to/file.pdf" }- Only
https://URLs are accepted. - The response is fetched with follow-redirects. Filename is derived from the response
Content-Dispositionheader, falling back to the URL path, then to"download". - Maximum size: 25 MiB (checked against
Content-Lengthfirst, then against actual bytes read).
5.3 Response
201 Created:
{
"url": "https://<supabase-project>.supabase.co/storage/v1/object/public/lead-attachments/<company_id>/<yyyy-mm>/<uuid>-<filename>",
"path": "<company_id>/<yyyy-mm>/<uuid>-<filename>",
"filename": "kitchen.pdf",
"size": 184320,
"content_type": "application/pdf"
}The returned url is stable and publicly readable. Pass it into the attachments array of a subsequent POST /api-leads call to attach it to a lead.
Error responses:
6. Error format
All non-2xx responses (except for OAuth errors on redirect URIs, which follow RFC 6749) use a single-key JSON body
{ "error": "Human-readable message describing the failure." }OAuth token/revoke endpoints additionally return a machine-readable code
{ "error": "invalid_grant", "error_description": "Authorization code expired" }CORS is enabled for all endpoints (Access-Control-Allow-Origin: *) so browser-based integrations can call them directly.
7. Rate limits and quotas
- Access token TTL: 1 hour. Refresh before expiry using the refresh token grant.
- Refresh token TTL: 30 days from initial issuance. Rotated on every use.
- Authorization code TTL: 5 minutes, single-use.
- Attachment size: 25 MiB per file.
- Attachment count: 20 URLs per lead.
- Custom fields: 50 keys, 32 KiB serialized total, 120 chars per key, 2000 chars per value.
- List page size: 200 records per response.
There is no hard per-minute rate limit at the API layer today. Traffic that materially exceeds normal polling patterns (one poll every 1–5 minutes per connection) will be reviewed and may be throttled at the platform layer. Please contact support before running batch backfills so we can whitelist your source IP.
8. Support and account management
- Application registration and credential rotation:
support@stonepad.co.uk - Endpoint status / incident notifications:
status@stonepad.co.uk - Response time: business hours (Monday–Friday, 09:00–17:30 UK).
Include your client_id (never the client_secret) and, where applicable, an example request/response pair when reporting integration issues.
9. Versioning and change policy
This is v1 of the Integration API. Backwards-incompatible changes will be published under a new base path (e.g. /v2/) with at least 90 days of parallel availability. Additive changes — new optional request fields, new response fields, new endpoints, new scopes — may ship at any time and will be announced in advance to registered application owners at the address on record.




