Hi all,
We’re building a public app (HeyRuby.ai) that lets customers connect their HubSpot CRM and import structured data. We want to model that data as a Custom Object and associate it to Deals and Companies.
Problem
When we try to create the custom object schema from our public app, the API returns 403 MISSING_SCOPES.
Endpoints tried
- POST /crm-object-schemas/v3/schemas
- POST /crm/v3/schemas
Error (example)
Status: 403
{
"status": "error",
"message": "The scope needed for this API call isn't available for public use.",
"category": "MISSING_SCOPES",
"correlationId": "81c4fd00-c610-49ab-b4b6-6c9491ed0be3"
}
Notes
- It fails with an OAuth token from the public app installed on a customer portal.
- We can create records of existing custom objects and use Associations v4 fine—the blocker is creating the schema itself from a public app.
What we need
- Is there a way for a public app to get Custom Objects OAuth access so we can request schema write scopes and create the schema programmatically?
- If yes, what’s the process to enable it (and any eligibility requirements)?
- If not, what’s the official workaround HubSpot recommends for public apps (beyond asking admins to create the schema manually or via a private app)?
Repro (minimal)
curl -i https://api.hubapi.com/crm-object-schemas/v3/schemas \
-H "Authorization: Bearer <OAUTH_ACCESS_TOKEN_FROM_PUBLIC_APP>" \
-H "Content-Type: application/json" \
-d '{
"name":"ruby_plan",
"labels":{"singular":"Ruby Plan","plural":"Ruby Plans"},
"primaryDisplayProperty":"name",
"requiredProperties":["name"],
"properties":[
{"name":"name","label":"Name","type":"string","fieldType":"text","isPrimaryDisplayLabel":true},
{"name":"status","label":"Status","type":"enumeration","fieldType":"select",
"options":[{"label":"Draft","value":"draft"},{"label":"Live","value":"live"}]},
{"name":"start_date","label":"Start Date","type":"date","fieldType":"date"}
],
"associatedObjects":["DEAL","COMPANY"],
"metaType":"PORTAL_SPECIFIC"
}'
Goal behavior (once allowed)
- Idempotent: read schema first; create only if missing.
- Namespaced: vendor-prefixed names (e.g., ruby_*).
- Non-destructive: no deletes/renames on customer data.
- Only attempt on eligible (Enterprise) portals; otherwise fall back.
App details available on request (App ID, volumes, etc.).
Would appreciate guidance on enabling this or the recommended path forward. Thanks!