{"version":"2026.07.21","gateway_base_url":"https://cloud.projexlight.com","note":"Compact SDK map for capability discovery. Match a capability to an SDK via reuse_when, then fetch that endpoint's full spec (payload/fields) with the get_sdk_api tool or from sdk-catalog.json. Do not rebuild what an SDK covers.","auth":{"model":"Four principals, each with its own credential: PLATFORM OPERATOR (ADMIN_OPS_TOKEN, /admin/* only, rejects tenant JWTs); TENANT ADMIN (human, password -> JWT); TENANT APPLICATION (machine, pk_live_/pk_test_ API key); APP END USER (human, password -> JWT scoped to an app_id). The gateway is DEFAULT-DENY: every path needs a valid tenant JWT unless explicitly public.","critical":["The API key and the JWT use the SAME header: 'Authorization: Bearer <value>'. The gateway distinguishes them by the pk_live_/pk_test_ prefix. There is NO X-API-Key header.","POST /api/auth/signup-tenant provisions an ENTIRE TENANT (org + app + tenant + membership). NEVER call it to register an end user of an app — use /api/auth/register.","Credential-management routes (/api/applications, /api/api-keys) require a HUMAN JWT and reject an API key by design: a key that can mint another key cannot be contained.","Do NOT build your own users/roles/sessions tables. identity + persona + tenant + rebac + policy already own that; a parallel table breaks tenant isolation and the audit chain.","A human JWT carries NO scopes. Authority is resolved from the persona and its grants at request time, so a revoked role takes effect immediately rather than at token expiry.","A MACHINE credential is different: its token DOES carry scopes and the gate enforces them. Scopes are <domain>.<resource>.<action> — THREE DOT-SEPARATED PARTS, derived from the request path (action = read for GET/HEAD else write, both segments singularised): GET /api/crm/contacts needs crm.contact.read; POST /api/sla/clocks needs sla.clock.write; GET /api/sequences needs sequence.sequence.read (a domain with no sub-segment doubles as its own resource). Tail wildcards work and are usually what you want: crm.* covers every CRM resource and action. THE VALIDATOR ACCEPTS ANY NON-EMPTY STRING ARRAY, so ['crm'] or ['crm:read'] is stored happily and then satisfies NOTHING — every call 403s. If a key 403s everywhere, read the body first: it names required_scopes and granted_scopes.","A machine credential is ALSO bound to a synthetic persona (created with the key, in the same transaction) so machine traffic is attributable as actor {kind: service} instead of borrowing a human's. It starts with NO role grants. Routes that authorize on tenant + scope — most reads — work immediately. A route that consults ReBAC or a role template 403s WITHOUT naming a scope; that is the case that needs POST /api/role-assignments {persona_id, role_template_id}.","POST /api/auth/token: client_id is OPTIONAL and the client_secret alone identifies the key, so omitting it is safest. If sent it must be the APPLICATION's slug or application_id — NOT the tenant's app_id, which is a different row. A mismatch is a hard 401 invalid_client, not a warning.","Everything downstream keys on persona_id (L4), not on person_id and not on a user_id."],"flows":{"tenant_signup":["POST /api/auth/signup-tenant  -> person + alias + credential + org + app + tenant + tenant_membership + app_identity + profile band (one transaction)","POST /api/auth/send-verification-email  -> purpose-scoped short-lived token","POST /api/auth/verify-email","POST /api/auth/login  (optional tenant_id selects the membership)  -> six-layer JWT"],"register_an_application_and_key":["POST /api/applications                        (human JWT) -> application_id","POST /api/applications/{application_id}/keys  (human JWT) -> pk_live_/pk_test_","POST /api/auth/token                          (public, client_credentials) -> short-lived token","rotate: POST /api/api-keys/{key_id}/rotate | revoke: POST /api/api-keys/{key_id}/revoke","POST /api/role-assignments {persona_id, role_template_id}   <- REQUIRED. The key's synthetic persona has no grants until you add them, so every call 403s no matter how valid the key is. persona_id is the key's synthetic_persona_id from the issue call; role_template_id names a tenant.role_template, whose app_id is an app_id and NOT the application_id above.","environment (live|test) is a property of the APPLICATION, not the key, so a test app can never mint a credential that reaches production data.","app_id IS NOT application_id. app_id is TEXT (e.g. 'leadflow-dev-af4bd2'), the PK of tenant.app; tenant.tenant.app_id and tenant.role_template.app_id both REFERENCE it, and persona.app_identity.app_id carries the same value. application_id is a UUID, the PK of api_keys.application - the API-KEY CLIENT REGISTRATION, whose slug is the client_id for client_credentials. Passing an application_id where an app_id belongs is refused by role_template_app_id_fkey, whose message names neither concept. app_id answers 'which of your products is this?'; application_id answers 'which of your machines is calling?'."],"app_end_user_signup":["POST /api/auth/register                       -> identity.person (+ alias, credential)","POST /api/app-identities                      -> app_identity, UNIQUE (person_id, app_id)","POST /api/app-identities/{id}/memberships     -> tenant + bu_id + starting role_template_id","POST /api/personas                            -> persona_id  (the acting identity)","POST /api/auth/login                          -> end-user JWT","One human in two of your apps is ONE person_id with TWO app_identities and TWO personas — not two accounts. Delete at persona level so other apps are unaffected."],"person_joins_another_tenant":["SUPPORTED, and it needs no new person. identity.alias has UNIQUE (kind, value_hash), so one email is one identity.person GLOBALLY — a human who is already a tenant admin somewhere is the same person_id when they join a different tenant's app.","POST /api/memberships                        -> tenant_membership for the OTHER tenant","POST /api/app-identities                     -> app_identity for that tenant's app_id","POST /api/memberships/{membership_id}/personas -> a SECOND persona, independent roles","Their existing personas are untouched. Login with tenant_id selects which membership the JWT is minted against; omit it to get a person-level token and let them choose.","DO NOT call /api/auth/register again for this person — it will fail on the unique alias, and that failure is the constraint doing its job."],"app_user_becomes_a_provider":["RESOLVED (EP-328). POST /api/auth/signup-tenant now REUSES the existing person when the caller sends a verified token for THAT SAME person; anonymous callers still get PersonExistsError. A second identity.person for one human would split their audit trail across two ids that nothing can reconcile, so reuse is the only safe repair.","Compose it instead, from an authorised caller:","POST /api/tenants                            -> the new tenant (+ its tenant.app row)","POST /api/memberships                        -> bind the EXISTING person_id to it","POST /api/memberships/{membership_id}/personas -> owner persona + owner role_template","POST /api/applications                       -> their application, then keys","Correct fix if you need self-service: make signup-tenant reuse the existing person_id when the alias matches AND the caller proves control of it (verified session or re-auth), rather than refusing. Creating a second person for the same human would break MDM convergence and split their audit trail."],"single_login_many_providers":["SUPPORTED. A person has ONE identity.credential, so one password works across every provider's app they belong to. Which app they enter is chosen at LOGIN, not signup.","POST /api/auth/login {email,password}                  -> person-level token (no tenant claims)","GET  /api/memberships                                  -> every tenant+app they are subscribed to","POST /api/auth/login {email,password,tenant_id,app_id} -> scoped token; app_identity auto-mints","Subscription is PROVIDER-controlled: login with a tenant_id returns 403 NoMembership unless a membership exists, so nobody self-joins by guessing an id. The provider admits them via POST /api/memberships.","CAVEAT: the credential is global, so a provider collecting the password on their own page holds one that works at other providers too. For mutually-untrusting providers, host login centrally and redirect. There is no OIDC authorize endpoint yet."],"get_an_api_key":["An API key belongs to an APPLICATION, which belongs to a TENANT. Order matters.","POST /api/auth/login {email,password,tenant_id}        -> human JWT (steps below need it)","POST /api/applications {name,slug,environment}         -> application_id","POST /api/applications/{application_id}/keys           -> pk_live_/pk_test_ (SHOWN ONCE)","call:  Authorization: Bearer pk_live_...   (same header as a JWT)","POST /api/auth/token (client_credentials)              -> short-lived token, preferred when the credential would otherwise sit on a device or in a browser","rotate POST /api/api-keys/{key_id}/rotate | revoke POST /api/api-keys/{key_id}/revoke | kill all POST /api/applications/{application_id}/disable","environment (live|test) is fixed on the APPLICATION and cannot be flipped; the prefix derives from it, so a test app can never mint a credential that reaches production.","The secret is returned ONCE — only a hash is stored, so there is no 'show it again' call.","Mint ONE KEY PER CONSUMER (web backend, mobile BFF, each CI pipeline) so revocation is about one consumer rather than an outage for all of them."],"roles_and_access":["RBAC  tenant.role_template keyed (tenant_id, app_id, name); tenant_id NULL = a platform default for the app, tenant_id set = that tenant's override of the same role name. parent_role_template_id gives inheritance.","POST /api/role-assignments {persona_id, role_template_id}  -> grant beyond the starting template","GET  /api/personas/{persona_id}/roles         -> LIST what a persona holds (read-only; there is no POST on this path)","POST /api/role-assignments/{assignment_id}/revoke          -> withdraw a grant","ReBAC (sdk-rebac)  'may THIS persona act on THAT record' — owner/delegate/account team relationships, with trust state and evidence. Use when authority comes from a relationship rather than from a role.","ABAC (sdk-policy)  'do the attributes permit it right now' — region, consent, time, record state. Use when the decision does not depend on identity.","They compose RBAC -> ReBAC -> ABAC."]},"multi_app_caveat":"tenant.tenant.app_id is NOT NULL, so a tenant row belongs to exactly ONE app, while tenant.app_pool_index (jsonb app->pool) assumes several. To model one tenant owning several apps today, use the tenant hierarchy (parent_tenant_id / root_tenant_id): a root tenant per customer and a child tenant per app. Do not assume one tenant row can span apps.","guide":"docs/v3.1/developer-hub/authentication.html"},"sdk_count":77,"api_count":639,"sdks":[{"sdk":"registry-mcp","group":"Platform & Multi-Tenancy","summary":"MCP server registry: tool registration, discovery and invocation brokering","reuse_when":["mcp","tool registry","server registration","tool discovery","agent tools"],"api_count":1},{"sdk":"sdk-asset","group":"Platform & Multi-Tenancy","summary":"Digital asset records and lifecycle","reuse_when":["asset","file","attachment","resource"],"api_count":5},{"sdk":"sdk-assignment","group":"Platform & Multi-Tenancy","summary":"Generic assignment of work/records to owners","reuse_when":["assignment","assign","owner","allocation"],"api_count":16},{"sdk":"sdk-command","group":"Platform & Multi-Tenancy","summary":"Command bus / imperative operation dispatch","reuse_when":["command","action","dispatch","operation"],"api_count":5},{"sdk":"sdk-config","group":"Platform & Multi-Tenancy","summary":"Tenant-scoped configuration values and typed settings resolution","reuse_when":["config","setting","tenant configuration","preference","parameter","feature configuration"],"api_count":6},{"sdk":"sdk-device","group":"Platform & Multi-Tenancy","summary":"Device registry, posture, and management","reuse_when":["device","hardware","posture","registration","mobile device"],"api_count":6},{"sdk":"sdk-diagnostic-telemetry","group":"Platform & Multi-Tenancy","summary":"Diagnostic telemetry ingestion and health signals","reuse_when":["diagnostic","telemetry","health","metrics","signal"],"api_count":6},{"sdk":"sdk-dispatch","group":"Platform & Multi-Tenancy","summary":"Dispatch/queue of jobs to workers","reuse_when":["dispatch","queue","job","worker","routing"],"api_count":2},{"sdk":"sdk-feature-flags","group":"Platform & Multi-Tenancy","summary":"Feature flags, targeting, and rollout","reuse_when":["feature flag","toggle","rollout","experiment","targeting"],"api_count":6},{"sdk":"sdk-geo","group":"Platform & Multi-Tenancy","summary":"Geospatial: geocoding, regions, distance, merge","reuse_when":["geo","location","geocode","address","distance","region","map"],"api_count":6},{"sdk":"sdk-media","group":"Platform & Multi-Tenancy","summary":"Media storage and transcoding (blob service)","reuse_when":["media","image","video","upload","blob","storage","transcode"],"api_count":5},{"sdk":"sdk-tenant","group":"Platform & Multi-Tenancy","summary":"Tenant records, settings, and membership","reuse_when":["tenant","organization","workspace","account","multi-tenant","tenant signup","onboarding","provisioning","business unit","role template","tenant admin","isolation tier","parent tenant","become a provider","sub tenant"],"api_count":10},{"sdk":"sdk-tenant-lifecycle","group":"Platform & Multi-Tenancy","summary":"Tenant provisioning, suspension, and offboarding lifecycle","reuse_when":["tenant lifecycle","provisioning","onboarding","suspend","offboard"],"api_count":5},{"sdk":"sdk-webhook","group":"Platform & Multi-Tenancy","summary":"Outbound webhooks: endpoints, deliveries, signing","reuse_when":["webhook","callback","event delivery","subscription","signature"],"api_count":7},{"sdk":"sdk-workflow","group":"Platform & Multi-Tenancy","summary":"Durable workflow/saga orchestration","reuse_when":["workflow","saga","orchestration","state machine","process","steps"],"api_count":4},{"sdk":"connector-twilio-voice","group":"Outreach & Communication","summary":"Twilio programmable voice: calls, IVR, recordings, consent-aware dialing","reuse_when":["voice","call","phone","twilio","ivr","dial","telephony","recording"],"api_count":8},{"sdk":"sdk-conversation","group":"Outreach & Communication","summary":"Omnichannel threading: unified inbox, reply detection, internal notes and compose guardrails","reuse_when":["conversation","thread","omnichannel","inbox","transcript","internal note","reply detection","compose guardrail","message history"],"api_count":5},{"sdk":"sdk-deliverability","group":"Outreach & Communication","summary":"Email/SMS deliverability: domains, warmup, suppression, bounce/complaint handling","reuse_when":["deliverability","email","domain","warmup","suppression","bounce","spam","dkim","spf"],"api_count":18},{"sdk":"sdk-handoff","group":"Outreach & Communication","summary":"Human/agent handoff sagas — route, accept, resolve work","reuse_when":["handoff","escalation","transfer","routing","assignment","agent handoff"],"api_count":9},{"sdk":"sdk-incident","group":"Outreach & Communication","summary":"Incident tickets: create, triage, assign, status transitions, audit","reuse_when":["incident","ticket","triage","outage","on-call","alerting"],"api_count":8},{"sdk":"sdk-notification","group":"Outreach & Communication","summary":"Multi-channel notifications (email/SMS/push/in-app) + provider adapters + templates","reuse_when":["notification","email","sms","push","in-app","alert","message","template","provider","frequency cap","dedup","quiet hours","send throttle","no answer retry","do not contact too often"],"api_count":21},{"sdk":"sdk-offer-catalog","group":"Outreach & Communication","summary":"Offers, quotes, pricing, feature-status matrix, publish gating","reuse_when":["offer","quote","pricing","catalog","proposal","package","plan"],"api_count":13},{"sdk":"sdk-scheduling","group":"Outreach & Communication","summary":"Calendar, appointments, availability, booking, no-show, public booking links","reuse_when":["scheduling","appointment","booking","calendar","availability","meeting","no-show","reschedule"],"api_count":31},{"sdk":"sdk-sequence","group":"Outreach & Communication","summary":"Multi-touch outreach sequences/cadences: steps, enrollment, advancement","reuse_when":["sequence","cadence","drip","outreach","follow-up","multi-touch","enrollment"],"api_count":12},{"sdk":"sdk-campaign","group":"CRM & Engagement","summary":"Marketing campaigns and membership","reuse_when":["campaign","marketing","blast","audience","segment"],"api_count":6},{"sdk":"sdk-content","group":"CRM & Engagement","summary":"Content items, templates, and rendering","reuse_when":["content","template","cms","copy"],"api_count":7},{"sdk":"sdk-coverage","group":"CRM & Engagement","summary":"Who is available to take work: schedules, PTO, holidays, on-call and capacity","reuse_when":["coverage","availability","pto","on-call","capacity","who is working","holiday","out of office","shift"],"api_count":16},{"sdk":"sdk-crm","group":"CRM & Engagement","summary":"Contacts, deals, pipelines, and activities (canonical CRM)","reuse_when":["crm","contact","deal","lead","pipeline","opportunity","account","activity"],"api_count":28},{"sdk":"sdk-engagement","group":"CRM & Engagement","summary":"Engagement events/timeline across channels","reuse_when":["engagement","timeline","interaction","activity","touchpoint"],"api_count":10},{"sdk":"sdk-event","group":"CRM & Engagement","summary":"Domain event store / event sourcing","reuse_when":["event","event store","domain event","stream"],"api_count":4},{"sdk":"sdk-lead-scoring","group":"CRM & Engagement","summary":"Lead scoring models, scoring, and next-best-action","reuse_when":["lead scoring","score","ml model","next best action","prioritize","ranking","firmographic","intent signal","company size","b2b features","prioritise leads"],"api_count":9},{"sdk":"sdk-service-request","group":"CRM & Engagement","summary":"Service requests / support tickets","reuse_when":["service request","ticket","support","case","request"],"api_count":5},{"sdk":"sdk-sla","group":"CRM & Engagement","summary":"Service-level clocks: targets, business hours, pause/resume, breach and escalation","reuse_when":["sla","response time","escalation","business hours","breach","time to first response","due by","overdue"],"api_count":30},{"sdk":"sdk-social","group":"CRM & Engagement","summary":"Social profiles and posting","reuse_when":["social","post","profile","social media"],"api_count":4},{"sdk":"sdk-import","group":"Data Provenance & Ingest","summary":"Governed bulk import: column mapping templates, dry run, commit and rollback","reuse_when":["import","mapping","csv","dry run","rollback","bulk upload","column mapping","spreadsheet","data migration"],"api_count":14},{"sdk":"sdk-parsing","group":"Data Provenance & Ingest","summary":"Contact extraction from unstructured text; proposes candidates with evidence spans and persists nothing","reuse_when":["contact extraction","smart paste","business card","vcard","email signature","ocr","parse contact","extract from text"],"api_count":3},{"sdk":"sdk-projection","group":"Data Provenance & Ingest","summary":"Attribute-level survivorship: which asserted value wins, with an explainable reason and deterministic replay","reuse_when":["survivorship","which value wins","explain projection","attribute conflict","losing assertion","conflicting data","replay","why is this field wrong"],"api_count":4},{"sdk":"sdk-source-record","group":"Data Provenance & Ingest","summary":"Provenance capture: immutable source records, origin class, raw evidence and chain of custody","reuse_when":["provenance","source record","origin","attestation","chain of custody","where did this come from","audit trail of a value","who told us this"],"api_count":13},{"sdk":"sdk-approval","group":"Governance, Consent & Security","summary":"Approval workflows, multi-step sign-off, audited break-glass","reuse_when":["approval","sign-off","review","break-glass","gate","authorization workflow"],"api_count":11},{"sdk":"sdk-audit","group":"Governance, Consent & Security","summary":"Tamper-evident audit chain / audit log","reuse_when":["audit","audit log","compliance","trail","tamper-evident","history"],"api_count":6},{"sdk":"sdk-consent","group":"Governance, Consent & Security","summary":"Consent capture, purpose binding, revocation, consent-gated auth","reuse_when":["consent","opt-in","opt-out","gdpr","purpose","permission","privacy"],"api_count":10},{"sdk":"sdk-data-rights","group":"Governance, Consent & Security","summary":"Data subject rights (DSAR): access, erasure, portability","reuse_when":["data rights","dsar","gdpr","erasure","right to be forgotten","subject access","privacy"],"api_count":9},{"sdk":"sdk-evidence","group":"Governance, Consent & Security","summary":"Evidence capture and chain-of-custody","reuse_when":["evidence","chain of custody","proof","forensic"],"api_count":3},{"sdk":"sdk-policy","group":"Governance, Consent & Security","summary":"Policy decision point (ABAC/PDP) with obligations","reuse_when":["policy","abac","pdp","authorization","decision","rule","obligation","access policy","permit or deny","attribute based","conditional access","guardrail"],"api_count":5},{"sdk":"sdk-rebac","group":"Governance, Consent & Security","summary":"Relationship-based access control (ReBAC) graph","reuse_when":["rebac","authorization","relationship","access control","permission","graph","contextual role","delegate","acts on behalf of","trust state","evidence","who may act for whom","can this user","ownership","sharing","team access"],"api_count":9},{"sdk":"sdk-secrets","group":"Governance, Consent & Security","summary":"Secret storage and retrieval","reuse_when":["secret","credential","token","secure store"],"api_count":5},{"sdk":"sdk-trace","group":"Governance, Consent & Security","summary":"Distributed trace / lineage records","reuse_when":["trace","lineage","span","distributed tracing","provenance"],"api_count":4},{"sdk":"sdk-vault","group":"Governance, Consent & Security","summary":"Encrypted vault for secrets/keys (envelope encryption)","reuse_when":["vault","secret","encryption","key","kms","envelope"],"api_count":8},{"sdk":"sdk-api-keys","group":"Identity & Access (AIM)","summary":"API key issuance, rotation, and scopes","reuse_when":["api key","token","credential","key rotation","scope","service account","application","client credentials","machine to machine","m2m","secret key","pk_live","publishable key","server to server"],"api_count":15},{"sdk":"sdk-identity","group":"Identity & Access (AIM)","summary":"Six-layer canonical identity: persons, credentials, authentication","reuse_when":["identity","user","auth","login","person","credential","authentication","account","signup","sign up","register","registration","sign in","session","password","email verification","forgot password","jwt","bearer token","who is logged in","same person across tenants","one email one person"],"api_count":21},{"sdk":"sdk-identity-resolver","group":"Identity & Access (AIM)","summary":"Identity resolution / EMPI / MDM matching and merge","reuse_when":["identity resolution","empi","mdm","match","dedupe","merge","golden record"],"api_count":8},{"sdk":"sdk-persona","group":"Identity & Access (AIM)","summary":"Personas — role-scoped identity facets (L4)","reuse_when":["persona","role","identity facet","actor","profile role","app user","end user","member","seat","user role","acting identity","persona id","one user many apps","join another tenant","switch tenant"],"api_count":17},{"sdk":"sdk-profile","group":"Identity & Access (AIM)","summary":"User/persona profiles and preferences","reuse_when":["profile","preferences","user profile","settings"],"api_count":6},{"sdk":"sdk-agent-runtime","group":"AI & Agents","summary":"Agent runtime: plan/execute, capability graph, capability tokens","reuse_when":["agent","ai agent","runtime","plan","execute","autonomous","capability","tool"],"api_count":12},{"sdk":"sdk-ai-gateway","group":"AI & Agents","summary":"Multi-LLM AI gateway with tenant BYOK provider keys","reuse_when":["ai","llm","openai","anthropic","model","completion","byok","provider key","inference"],"api_count":10},{"sdk":"sdk-ingest","group":"AI & Agents","summary":"Data ingestion pipelines","reuse_when":["ingest","ingestion","import","etl","pipeline","load data"],"api_count":3},{"sdk":"sdk-mcp-bridge","group":"AI & Agents","summary":"MCP bridge — expose tools to agents via the Model Context Protocol","reuse_when":["mcp","tool","agent tool","bridge","model context protocol"],"api_count":6},{"sdk":"sdk-search","group":"AI & Agents","summary":"Search / query across resources","reuse_when":["search","query","find","full-text","lookup"],"api_count":6},{"sdk":"sdk-taxonomy","group":"AI & Agents","summary":"Taxonomies, categories, and classification","reuse_when":["taxonomy","category","classification","tag","hierarchy"],"api_count":4},{"sdk":"semantic-service","group":"AI & Agents","summary":"Semantic/ontology + embeddings + vector search (RAG)","reuse_when":["semantic","embedding","vector","ontology","similarity","rag","knowledge"],"api_count":13},{"sdk":"hdk-camera","group":"Native HDK (mobile / edge)","summary":"On-device camera capture (HDK)","reuse_when":["camera","capture","photo","scan","mobile camera"],"api_count":2},{"sdk":"hdk-diagnostic","group":"Native HDK (mobile / edge)","summary":"On-device diagnostics (HDK)","reuse_when":["diagnostic","device diagnostic","hardware check"],"api_count":2},{"sdk":"hdk-idp","group":"Native HDK (mobile / edge)","summary":"On-device identity/document proofing (HDK)","reuse_when":["idp","id verification","document proofing","kyc","id scan"],"api_count":3},{"sdk":"hdk-image-editor","group":"Native HDK (mobile / edge)","summary":"On-device image editing (HDK)","reuse_when":["image editor","crop","edit image","annotate"],"api_count":1},{"sdk":"hdk-map","group":"Native HDK (mobile / edge)","summary":"On-device maps (HDK)","reuse_when":["map","navigation","location","offline map"],"api_count":2},{"sdk":"hdk-measure","group":"Native HDK (mobile / edge)","summary":"On-device AR measurement (HDK)","reuse_when":["measure","ar","dimension","distance","measurement"],"api_count":3},{"sdk":"hdk-permissions","group":"Native HDK (mobile / edge)","summary":"On-device permission prompts (HDK)","reuse_when":["permissions","device permission","access prompt"],"api_count":2},{"sdk":"hdk-scanner","group":"Native HDK (mobile / edge)","summary":"On-device barcode/QR scanner (HDK)","reuse_when":["scanner","barcode","qr","scan code"],"api_count":1},{"sdk":"hdk-sync","group":"Native HDK (mobile / edge)","summary":"Offline-first data sync (HDK)","reuse_when":["sync","offline","offline-first","replication","conflict"],"api_count":8},{"sdk":"hdk-video-editor","group":"Native HDK (mobile / edge)","summary":"On-device video editing (HDK)","reuse_when":["video editor","trim","edit video"],"api_count":1},{"sdk":"hdk-watermark","group":"Native HDK (mobile / edge)","summary":"On-device watermarking (HDK)","reuse_when":["watermark","stamp","overlay","brand image"],"api_count":3},{"sdk":"sdk-connectors","group":"Connectors & Integration","summary":"Enterprise connector framework (Salesforce, Slack, M365, Snowflake, …)","reuse_when":["connector","integration","salesforce","slack","m365","snowflake","sync","third-party","external system","lead form","meta","facebook","instagram","linkedin","tiktok","google ads","web chat","webhook ingest"],"api_count":22},{"sdk":"sdk-analytics","group":"Billing, Metering & Analytics","summary":"Analytics rollups and reporting","reuse_when":["analytics","report","metrics","dashboard","aggregate","insight"],"api_count":6},{"sdk":"sdk-billing","group":"Billing, Metering & Analytics","summary":"Invoicing and billing","reuse_when":["billing","invoice","charge","subscription","bill"],"api_count":4},{"sdk":"sdk-data-credits","group":"Billing, Metering & Analytics","summary":"Prepaid capability credits: balances, reservation, consumption and top-up against enrichment providers","reuse_when":["credits","capability","enrichment","provider","budget","metered spend","top up","quota","prepaid"],"api_count":13},{"sdk":"sdk-meter","group":"Billing, Metering & Analytics","summary":"Usage metering / metered events","reuse_when":["meter","usage","metering","consumption","quota","usage-based"],"api_count":2},{"sdk":"sdk-payment","group":"Billing, Metering & Analytics","summary":"Payments and payment methods","reuse_when":["payment","pay","card","stripe","checkout","transaction"],"api_count":5}]}