Developer Hub › API Keys & Project Setup
Server-to-server access

Calling ProjexCloud from your own app

How to issue a tenant-scoped API key, what scopes it needs, and how to point a development project at ProjexCloud — locally or in the cloud — without ever putting a person's password in a config file.

1 · Which credential does your app use?

ProjexCloud accepts two credential classes on its tenant-callable SDK routes. Pick by who is acting.

API key machines

Your backend calling ProjexCloud server-to-server.
  • Scoped — grants only the domains you list
  • Rotatable with a 24-hour grace window
  • Revocable instantly across every gateway replica
  • Audited as actor.kind = service, so machine traffic is distinguishable from a human's
  • Survives password changes and MFA rollouts

Six-layer JWT people

A signed-in human in a portal or your UI.
  • Obtained from POST /api/auth/login
  • Carries persona, tenant and business-unit context
  • Authority comes from ReBAC grants, not a scope list
  • Short-lived; refreshed by the portal
Never put a user's email and password in a service's environment. It is the single most common integration mistake here. A human credential inherits every privilege that person holds, makes their audit trail indistinguishable from your service's, breaks the moment anyone enables MFA (see /api/mfa/challenge), and dies at the next password rotation. Use an API key.

2 · Generate an API key

Keys are issued by sdk-api-keys, mounted on the gateway. You need two things first: a user JWT (to authorise the issuance) and the tenant_id the key will belong to.

2

Create an application

curl -sX POST "$GATEWAY/api/applications" \
  -H "Authorization: Bearer $JWT" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Web backend","environment":"live"}'
# → { "data": { "application": { "application_id": "…", "slug": "web-backend", "environment": "live" } } }

One application per thing that calls the platform — your backend, a nightly job, your staging copy. A shared credential means a leak forces every integration to rotate at once and no call can be attributed to the app that made it. environment belongs to the application: a test application mints pk_test_ keys and a live one mints pk_live_, so the two can never be confused by inspection. The slug is your client_id.

3

Issue the key

curl -sX POST "$GATEWAY/api/applications/$APPLICATION_ID/keys" \
  -H "Authorization: Bearer $JWT" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "nightly sync",
    "scopes": ["sla.clock.write", "sla.clock.read", "assignment.assign-by-task.write"],
    "rate_limit_rpm": 600,
    "expires_at": "2027-01-01T00:00:00Z"
  }'

Answers 201 with both the key record and the plaintext:

{ "data": {
    "key": { "key_id": "…", "tenant_id": "…", "prefix": "pk_live_A1B2...WXYZ",
             "scopes": ["…"], "status": "active" },
    "plaintext": "pk_live_A1B2C3D4E5F6G7H8J9K…"
} }
plaintext is returned exactly once and is never recoverable. Only a keyed HMAC-SHA256 lookup and a display prefix are stored. Copy it into your secret store immediately. Lose it and your only option is to rotate. Never log it, never commit it, never paste it into a ticket.

rate_limit_rpm and expires_at are optional but recommended — an unbounded, never-expiring key is a standing liability. Give every integration its own key so one revocation cannot take down two apps.

Test keys

The format accepts a pk_test_ prefix alongside pk_live_. Issue separate keys for development and production so a leaked dev key can never touch live data.

3 · Scopes

Scopes follow <domain>.<resource>.<action> — for example crm.contact.read, rebac.relationship.write. The scope a request requires is derived from the route itself — on every tenant route, not a subset — so it is predictable without a lookup table, and a route added tomorrow is covered the moment it exists:

RuleValue
domainThe SDK's namespace, taken from the path — sla, crm, coverage, and so on for every mounted SDK
resourceThe path segment after the domain, singularised (clocksclock, policiespolicy). Path parameters are skipped.
actionread for GET/HEAD, write for everything else

A missing scope answers 403 and tells you exactly which one is absent, along with what the key does hold — so the fastest way to discover the scope list you need is to call the endpoint and read the error.

Scopes constrain API keys only. A JWT caller's authority comes from their persona and ReBAC grants, so adding key support to a route changes nothing for existing human traffic.

Generated from tests/api_definitions by scripts/verify/gen-scope-table.mjs — 524 endpoints across 90 domains. Do not edit by hand. A domain wildcard such as sla.* covers every row in that domain, including ones added later.

RequestScope required
GET /api/agent-runtime/agentsagent-runtime.agent.read
GET /api/agent-runtime/agents/:idagent-runtime.agent.read
GET /api/agent-runtime/healthagent-runtime.health.read
GET /api/agent-runtime/runsagent-runtime.run.read
GET /api/agent-runtime/runs/:idagent-runtime.run.read
POST /api/agent-runtime/agentsagent-runtime.agent.write
POST /api/agent-runtime/runsagent-runtime.run.write
POST /api/agent-runtime/runs/:run_id/replayagent-runtime.run.write
POST /api/agent-runtime/runs/:run_id/rollbackagent-runtime.run.write
POST /api/agent-runtime/tokensagent-runtime.token.write
POST /api/agent-runtime/tokens/:token_id/revokeagent-runtime.token.write
POST /api/agent-runtime/tokens/:token_id/validateagent-runtime.token.write
DELETE /api/ai-gateway/tenant-credentials/:binding_idai-gateway.tenant-credential.write
GET /api/ai-gateway/healthai-gateway.health.read
GET /api/ai-gateway/tenant-credentialsai-gateway.tenant-credential.read
PATCH /api/ai-gateway/tenant-credentials/:binding_idai-gateway.tenant-credential.write
POST /api/ai-gateway/completeai-gateway.complete.write
POST /api/ai-gateway/streamai-gateway.stream.write
POST /api/ai-gateway/tenant-credentialsai-gateway.tenant-credential.write
GET /api/analytics/datasetsanalytic.dataset.read
GET /api/analytics/datasets/:spec_id/buildsanalytic.dataset.read
POST /api/analytics/builds/:build_id/exportanalytic.build.write
POST /api/analytics/datasetsanalytic.dataset.write
POST /api/analytics/datasets/:spec_id/buildanalytic.dataset.write
PUT /api/analytics/datasets/:spec_id/label-sourceanalytic.dataset.write
GET /api/api-keysapi-key.api-key.read
POST /api/api-keysapi-key.api-key.write
POST /api/api-keys/{key_id}/revokeapi-key.revoke.write
POST /api/api-keys/{key_id}/rotateapi-key.rotate.write
GET /api/app-identities/:app_identity_idapp-identity.app-identity.read
GET /api/app-identities/:app_identity_id/membershipsapp-identity.membership.read
POST /api/app-identitiesapp-identity.app-identity.write
GET /api/applicationsapplication.application.read
GET /api/applications/{application_id}application.application.read
POST /api/applicationsapplication.application.write
POST /api/applications/{application_id}/disableapplication.disable.write
POST /api/applications/{application_id}/keysapplication.key.write
GET /api/approvals/requestsapproval.request.read
GET /api/approvals/requests/:request_idapproval.request.read
GET /api/approvals/routesapproval.route.read
POST /api/approvals/requestsapproval.request.write
POST /api/approvals/requests/:request_id/decideapproval.request.write
POST /api/approvals/routesapproval.route.write
POST /api/approvals/steps/:step_id/decideapproval.step.write
GET /api/assets/:asset_id/commandsasset.command.read
GET /api/assets/:asset_id/readingsasset.reading.read
GET /api/assets/:asset_id/twinasset.twin.read
POST /api/assetsasset.asset.write
POST /api/assets/:asset_id/credentialsasset.credential.write
POST /api/assignment/assign-by-taskassignment.assign-by-task.write
PUT /api/assignment/workload/:persona_idassignment.workload.write
POST /api/audit/appendaudit.append.write
POST /api/audit/exportaudit.export.write
POST /api/audit/verifyaudit.verify.write
POST /api/auth/loginauth.login.write
POST /api/auth/registerauth.register.write
POST /api/auth/signup-tenantauth.signup-tenant.write
POST /api/auth/tokenauth.token.write
GET /api/billing/livebilling.live.read
GET /api/billing/showbackbilling.showback.read
POST /api/billing/invoices/generatebilling.invoice.write
POST /api/billing/reprice-dry-runbilling.reprice-dry-run.write
GET /api/break-glass/:grant_idbreak-glass.break-glass.read
POST /api/break-glassbreak-glass.break-glass.write
POST /api/break-glass/:grant_id/decidebreak-glass.decide.write
POST /api/break-glass/:grant_id/usebreak-glass.use.write
POST /api/build/planbuild.plan.write
POST /api/campaignscampaign.campaign.write
POST /api/campaigns/:campaign_id/journeyscampaign.journey.write
POST /api/campaigns/:campaign_id/segmentscampaign.segment.write
POST /api/campaigns/journeys/:journey_id/runscampaign.journey.write
POST /api/campaigns/runs/:run_id/advancecampaign.run.write
POST /api/campaigns/segments/:segment_id/computecampaign.segment.write
GET /api/commands/:command_idcommand.command.read
GET /api/commands/stream/:asset_idcommand.stream.read
POST /api/commandscommand.command.write
POST /api/commands/:command_id/decisioncommand.decision.write
GET /api/configconfig.config.read
GET /api/config/resolveconfig.resolve.read
GET /api/config/valueconfig.value.read
POST /api/configconfig.config.write
POST /api/config/revokeconfig.revoke.write
POST /api/config/rotateconfig.rotate.write
GET /api/connectorsconnector.connector.read
GET /api/connectors/installs/:install_idconnector.install.read
GET /api/connectors/installs/:install_id/healthconnector.install.read
GET /api/connectors/installs/:install_id/toolsconnector.install.read
GET /api/connectors/kindsconnector.kind.read
GET /api/connectors/tenants/:tenant_id/dlqconnector.tenant.read
GET /api/connectors/tenants/:tenant_id/installsconnector.tenant.read
POST /api/connectors/dlq/replayconnector.dlq.write
POST /api/connectors/dlq/retry-tickconnector.dlq.write
POST /api/connectors/inbound/:kindconnector.inbound.write
POST /api/connectors/installsconnector.install.write
POST /api/connectors/installs/:install_id/syncconnector.install.write
POST /api/connectors/installs/:install_id/tools/callconnector.install.write
POST /api/connectors/installs/:install_id/uninstallconnector.install.write
POST /api/connectors/slack/eventsconnector.slack.write
POST /api/connectors/slack/installconnector.slack.write
POST /api/connectors/slack/post-messageconnector.slack.write
POST /api/connectors/tenants/:tenant_id/dlq/reconcileconnector.tenant.write
GET /api/consent/purposesconsent.purpos.read
GET /api/consent/receiptsconsent.receipt.read
GET /api/consents/exportconsent.export.read
POST /api/consent/receipts/:receipt_id/revokeconsent.receipt.write
POST /api/consentsconsent.consent.write
POST /api/consents/:receipt_id/revokeconsent.revoke.write
POST /api/consents/checkconsent.check.write
POST /api/consents/purposesconsent.purpos.write
GET /api/content/items/:item_idcontent.item.read
GET /api/content/items/:item_id/versionscontent.item.read
POST /api/content/itemscontent.item.write
POST /api/content/items/:item_id/archivecontent.item.write
POST /api/content/items/:item_id/versionscontent.item.write
POST /api/content/items/:item_id/versions/:version_id/publishcontent.item.write
PUT /api/content/taxonomiescontent.taxonomy.write
GET /api/crm/activities/callscrm.activity.read
GET /api/crm/contacts/:contact_idcrm.contact.read
GET /api/crm/dealscrm.deal.read
GET /api/crm/deals/:deal_idcrm.deal.read
GET /api/crm/deals/:deal_id/next-actioncrm.deal.read
GET /api/crm/deals/:deal_id/save-gatecrm.deal.read
GET /api/crm/deals/:deal_id/stage-guardcrm.deal.read
GET /api/crm/funnel-stagescrm.funnel-stage.read
GET /api/crm/pipeline/boardcrm.pipeline.read
GET /api/crm/pipeline/stalecrm.pipeline.read
PATCH /api/crm/contacts/:contact_idcrm.contact.write
PATCH /api/crm/deals/:deal_idcrm.deal.write
POST /api/crm/activitiescrm.activity.write
POST /api/crm/activities/callcrm.activity.write
POST /api/crm/activities/voicemailcrm.activity.write
POST /api/crm/contactscrm.contact.write
POST /api/crm/dealscrm.deal.write
POST /api/crm/deals/:deal_id/next-actioncrm.deal.write
POST /api/crm/deals/:deal_id/next-action/completecrm.deal.write
POST /api/crm/deals/:deal_id/transitioncrm.deal.write
POST /api/crm/funnel-stagescrm.funnel-stage.write
GET /api/data-rights/requests/:request_iddata-right.request.read
GET /api/data-rights/residency/:person_iddata-right.residency.read
POST /api/data-rights/executions/:execution_id/resultdata-right.execution.write
POST /api/data-rights/reconciliation/rundata-right.reconciliation.write
POST /api/data-rights/requestsdata-right.request.write
POST /api/data-rights/requests/:request_id/certificatedata-right.request.write
POST /api/data-rights/requests/:request_id/plan-executionsdata-right.request.write
POST /api/data-rights/requests/:request_id/transitiondata-right.request.write
POST /api/data-rights/residency/touchdata-right.residency.write
GET /api/deliverability/bounce-eventsdeliverability.bounce-event.read
GET /api/deliverability/mailboxesdeliverability.mailbox.read
GET /api/deliverability/reply-eventsdeliverability.reply-event.read
GET /api/deliverability/reputationdeliverability.reputation.read
GET /api/deliverability/suppressionsdeliverability.suppression.read
POST /api/deliverability/checkdeliverability.check.write
POST /api/deliverability/mailboxesdeliverability.mailbox.write
POST /api/deliverability/mailboxes/:mailbox_id/repliesdeliverability.mailbox.write
POST /api/deliverability/mailboxes/:mailbox_id/syncdeliverability.mailbox.write
POST /api/deliverability/optout-tokensdeliverability.optout-token.write
POST /api/deliverability/optout/redeemdeliverability.optout.write
POST /api/deliverability/reputation/recorddeliverability.reputation.write
POST /api/deliverability/reputation/resumedeliverability.reputation.write
POST /api/deliverability/suppressionsdeliverability.suppression.write
POST /api/deliverability/suppressions/removedeliverability.suppression.write
POST /api/deliverability/webhook-secretsdeliverability.webhook-secret.write
POST /api/deliverability/webhooks/:providerdeliverability.webhook.write
GET /api/devices/:device_uuiddevice.device.read
GET /api/devices/:device_uuid/personsdevice.person.read
POST /api/devicesdevice.device.write
POST /api/devices/:device_uuid/attestdevice.attest.write
POST /api/devices/:device_uuid/link-persondevice.link-person.write
POST /api/devices/:device_uuid/revokedevice.revoke.write
GET /api/diagnostic/crashdiagnostic.crash.read
GET /api/diagnostic/crash/:iddiagnostic.crash.read
GET /api/diagnostic/healthdiagnostic.health.read
POST /api/diagnostic/crashdiagnostic.crash.write
POST /api/diagnostic/healthdiagnostic.health.write
POST /api/diagnostic/session-replaydiagnostic.session-replay.write
GET /api/dispatch/ws/:persona_iddispatch.w.read
POST /api/dispatch/routes/optimizedispatch.route.write
GET /api/empi/candidate-linksempi.candidate-link.read
GET /api/empi/metricsempi.metric.read
POST /api/empi/candidate-links/:link_id/adjudicateempi.candidate-link.write
POST /api/empi/candidate-links/:link_id/steward-reviewempi.candidate-link.write
POST /api/empi/mergesempi.merge.write
POST /api/empi/merges/:merge_id/unmergeempi.merge.write
GET /api/encounters/:encounter_idencounter.encounter.read
GET /api/encounters/:encounter_id/grantsencounter.grant.read
GET /api/encounters/:encounter_id/participantsencounter.participant.read
POST /api/encountersencounter.encounter.write
POST /api/encounters/:encounter_id/grantsencounter.grant.write
POST /api/encounters/:encounter_id/grants/checkencounter.grant.write
POST /api/encounters/:encounter_id/participantsencounter.participant.write
POST /api/encounters/:encounter_id/transitionencounter.transition.write
GET /api/events/sessions/:session_idevent.session.read
GET /api/events/typesevent.type.read
GET /api/events/types/:typeevent.type.read
POST /api/events/checkinevent.checkin.write
POST /api/events/sessionsevent.session.write
POST /api/events/ticketsevent.ticket.write
GET /api/evidence/captureevidence.capture.read
GET /api/evidence/capture/:idevidence.capture.read
POST /api/evidence/captureevidence.capture.write
GET /api/flagsflag.flag.read
GET /api/flags/:flag_idflag.flag.read
POST /api/flags/:flag_id/evaluateflag.evaluate.write
POST /api/flags/:flag_id/kill-switchflag.kill-switch.write
POST /api/flags/:flag_id/rolloutsflag.rollout.write
PUT /api/flagsflag.flag.write
GET /api/geo/addresses/:address_idgeo.address.read
POST /api/geo/bbox-querygeo.bbox-query.write
POST /api/geo/canonicalizegeo.canonicalize.write
POST /api/geo/geocodegeo.geocode.write
POST /api/geo/mergegeo.merge.write
POST /api/geo/reverse-geocodegeo.reverse-geocode.write
POST /api/geo-nodesgeo-node.geo-node.write
POST /api/grants/:grant_id/revokegrant.revoke.write
GET /api/handoffshandoff.handoff.read
GET /api/handoffs/:handoff_idhandoff.handoff.read
GET /api/handoffs/:handoff_id/sagahandoff.saga.read
PATCH /api/handoffs/:handoff_idhandoff.handoff.write
POST /api/handoffshandoff.handoff.write
POST /api/handoffs/:handoff_id/approval/decisionhandoff.approval.write
POST /api/handoffs/:handoff_id/approval/requesthandoff.approval.write
POST /api/handoffs/:handoff_id/saga/starthandoff.saga.write
POST /api/handoffs/:handoff_id/transitionhandoff.transition.write
GET /api/hdk/camera/capabilitieshdk.camera.read
GET /api/hdk/camera/recording-presetshdk.camera.read
GET /api/hdk/image-editor/capabilitieshdk.image-editor.read
GET /api/hdk/map/capabilitieshdk.map.read
GET /api/hdk/map/tile-providershdk.map.read
GET /api/hdk/measurehdk.measure.read
GET /api/hdk/measure/:idhdk.measure.read
GET /api/hdk/scanner/capabilitieshdk.scanner.read
GET /api/hdk/video-editor/capabilitieshdk.video-editor.read
GET /api/hdk/watermarkhdk.watermark.read
GET /api/hdk/watermark/:idhdk.watermark.read
POST /api/hdk/measurehdk.measure.write
POST /api/hdk/watermarkhdk.watermark.write
POST /api/hdk-diagnostic/drainhdk-diagnostic.drain.write
POST /api/hdk-diagnostic/eventshdk-diagnostic.event.write
GET /api/hdk-idp/devices/:device_uuid/claimshdk-idp.device.read
POST /api/hdk-idp/claimshdk-idp.claim.write
POST /api/hdk-idp/offline-auth/loghdk-idp.offline-auth.write
GET /api/hdk-permissions/devices/:device_uuid/latesthdk-permission.device.read
POST /api/hdk-permissions/snapshotshdk-permission.snapshot.write
GET /api/hdk-sync/event-type-policieshdk-sync.event-type-policy.read
GET /api/hdk-sync/event-type-policies/:event_typehdk-sync.event-type-policy.read
GET /api/hdk-sync/human-review/openhdk-sync.human-review.read
POST /api/hdk-sync/conflicts/resolvehdk-sync.conflict.write
POST /api/hdk-sync/human-review/:task_id/resolvehdk-sync.human-review.write
POST /api/hdk-sync/replay/:batch_id/completehdk-sync.replay.write
POST /api/hdk-sync/replay/starthdk-sync.replay.write
PUT /api/hdk-sync/event-type-policieshdk-sync.event-type-policy.write
POST /api/identity/aliasesidentity.alias.write
POST /api/identity/social/:provider/callbackidentity.social.write
POST /api/impersonation/:grant_id/approveimpersonation.approve.write
POST /api/impersonation/:grant_id/endimpersonation.end.write
POST /api/impersonation/requestimpersonation.request.write
GET /api/imports/mapping-templatesimport.mapping-template.read
GET /api/imports/runsimport.run.read
GET /api/imports/runs/:run_idimport.run.read
GET /api/imports/runs/:run_id/exceptionsimport.run.read
POST /api/imports/mapping-templatesimport.mapping-template.write
POST /api/imports/mapping-templates/:template_id/versionimport.mapping-template.write
POST /api/imports/runsimport.run.write
POST /api/imports/runs/:run_id/commitimport.run.write
POST /api/imports/runs/:run_id/dry-runimport.run.write
POST /api/imports/runs/:run_id/mapping-suggestionsimport.run.write
POST /api/imports/runs/:run_id/previewimport.run.write
POST /api/imports/runs/:run_id/rollbackimport.run.write
POST /api/imports/runs/:run_id/transform-planimport.run.write
PUT /api/imports/runs/:run_id/mappingimport.run.write
GET /api/incidentsincident.incident.read
GET /api/incidents/:incident_idincident.incident.read
GET /api/incidents/:incident_id/evidenceincident.evidence.read
GET /api/incidents/sla-breachesincident.sla-breache.read
PATCH /api/incidents/:incident_idincident.incident.write
POST /api/incidentsincident.incident.write
POST /api/incidents/:incident_id/evidenceincident.evidence.write
POST /api/incidents/:incident_id/transitionincident.transition.write
POST /api/ingest/:entity/batchingest.batch.write
POST /api/ingest/customer/batchingest.customer.write
POST /api/ingest/sensor-readings/batchingest.sensor-reading.write
GET /api/keyskey.key.read
POST /api/keyskey.key.write
POST /api/keys/:key_id/revokekey.revoke.write
GET /api/lead-scoring/models/:idlead-scoring.model.read
GET /api/lead-scoring/models/:id/weightslead-scoring.model.read
GET /api/lead-scoring/models/activelead-scoring.model.read
POST /api/lead-scoring/modelslead-scoring.model.write
POST /api/lead-scoring/models/:id/activatelead-scoring.model.write
POST /api/lead-scoring/models/:id/retirelead-scoring.model.write
POST /api/lead-scoring/next-best-actionlead-scoring.next-best-action.write
POST /api/lead-scoring/scorelead-scoring.score.write
PUT /api/lead-scoring/models/:id/weights/:featurelead-scoring.model.write
GET /api/mcp/healthmcp.health.read
GET /api/mcp/server-registrationsmcp.server-registration.read
GET /api/mcp/server-registrations/:idmcp.server-registration.read
POST /api/mcp/server-registrationsmcp.server-registration.write
POST /api/mcp/server-registrations/:id/disablemcp.server-registration.write
POST /api/mcp/tools/:tool_id/invokemcp.tool.write
PUT /api/me/profileme.profile.write
GET /api/media/:blob_id/playback-urlmedia.playback-url.read
GET /api/media/transcode-jobs/:job_idmedia.transcode-job.read
POST /api/media/:blob_id/readymedia.ready.write
POST /api/media/:blob_id/transcodemedia.transcode.write
POST /api/media/upload-urlmedia.upload-url.write
GET /api/memberships/:membership_id/personasmembership.persona.read
POST /api/membershipsmembership.membership.write
POST /api/memberships/:membership_id/terminatemembership.terminate.write
GET /api/meter/assets/:asset_id/usagemeter.asset.read
GET /api/meter/healthmeter.health.read
POST /api/mfa/challengemfa.challenge.write
POST /api/mfa/verifymfa.verify.write
DELETE /api/notifications/providers/:provider_idnotification.provider.write
GET /api/notifications/delivery-receiptsnotification.delivery-receipt.read
GET /api/notifications/providersnotification.provider.read
GET /api/notifications/sms-consentnotification.sms-consent.read
GET /api/notifications/sms-inboundnotification.sms-inbound.read
PATCH /api/notifications/providers/:provider_idnotification.provider.write
POST /api/notifications/dispatchnotification.dispatch.write
POST /api/notifications/providersnotification.provider.write
POST /api/notifications/providers/:provider_id/verifynotification.provider.write
POST /api/notifications/quiet-hoursnotification.quiet-hour.write
POST /api/notifications/sendnotification.send.write
POST /api/notifications/sms-consentnotification.sms-consent.write
POST /api/notifications/sms-settingsnotification.sms-setting.write
POST /api/notifications/templatesnotification.template.write
POST /api/notifications/webhooks/delivery/:providernotification.webhook.write
POST /api/notifications/webhooks/sms/inboundnotification.webhook.write
GET /api/offers/:offer_id/currentoffer.current.read
GET /api/offers/:offer_id/version-stampoffer.version-stamp.read
GET /api/offers/:offer_id/versions/:version_id/featuresoffer.version.read
POST /api/offersoffer.offer.write
POST /api/offers/:offer_id/check-referenceoffer.check-reference.write
POST /api/offers/:offer_id/versionsoffer.version.write
POST /api/offers/:offer_id/versions/:version_id/activateoffer.version.write
POST /api/offers/:offer_id/versions/:version_id/featuresoffer.version.write
POST /api/offers/:offer_id/versions/:version_id/publish-decisionoffer.version.write
POST /api/offers/:offer_id/versions/:version_id/publish-requestoffer.version.write
POST /api/participants/:participant_id/leaveparticipant.leave.write
GET /api/payments/providerpayment.provider.read
POST /api/payments/:charge_id/distributepayment.distribute.write
POST /api/payments/:charge_id/refundpayment.refund.write
POST /api/payments/chargepayment.charge.write
POST /api/payments/methodspayment.method.write
GET /api/persons/:person_id/app-identitiesperson.app-identity.read
GET /api/persons/:person_id/devicesperson.device.read
GET /api/personaspersona.persona.read
GET /api/personas/:persona_idpersona.persona.read
GET /api/personas/:persona_id/rolespersona.role.read
POST /api/personaspersona.persona.write
POST /api/personas/:persona_id/bupersona.bu.write
POST /api/personas/:persona_id/deactivatepersona.deactivate.write
POST /api/personas/:persona_id/rolepersona.role.write
POST /api/personas/:persona_id/shredpersona.shred.write
GET /api/policies/:policy_idpolicy.policy.read
POST /api/policiespolicy.policy.write
POST /api/policies/evaluatepolicy.evaluate.write
POST /api/principal-tokenprincipal-token.principal-token.write
GET /api/profile/bands/:app_identity_id/:band_kindprofile.band.read
GET /api/profile/secure-data/:person_idprofile.secure-data.read
GET /api/profile/secure-data/:person_id/shred-historyprofile.secure-data.read
POST /api/profile/secure-data/set-fieldprofile.secure-data.write
POST /api/profile/secure-data/shred-fieldprofile.secure-data.write
PUT /api/profile/bandsprofile.band.write
POST /api/relationshipsrelationship.relationship.write
POST /api/relationships/checkrelationship.check.write
PUT /api/relationships/:relationship_id/scoperelationship.scope.write
POST /api/resellersreseller.reseller.write
POST /api/resolver/explainresolver.explain.write
POST /api/resolver/resolveresolver.resolve.write
GET /api/resourcesresource.resource.read
GET /api/resources/:resource_idresource.resource.read
POST /api/resourcesresource.resource.write
POST /api/role-assignmentsrole-assignment.role-assignment.write
POST /api/role-assignments/:assignment_id/revokerole-assignment.revoke.write
POST /api/role-templatesrole-template.role-template.write
GET /api/router/resolverouter.resolve.read
GET /api/scheduling/appointmentsscheduling.appointment.read
GET /api/scheduling/appointments/:appointment_idscheduling.appointment.read
GET /api/scheduling/appointments/:appointment_id/eventsscheduling.appointment.read
GET /api/scheduling/appointments/:appointment_id/icsscheduling.appointment.read
GET /api/scheduling/appointments/:appointment_id/remindersscheduling.appointment.read
GET /api/scheduling/availabilityscheduling.availability.read
GET /api/scheduling/calendar-connectionsscheduling.calendar-connection.read
GET /api/scheduling/calendar-connections/:connection_idscheduling.calendar-connection.read
GET /api/scheduling/meeting-typesscheduling.meeting-type.read
GET /api/scheduling/public/links/:slugscheduling.public.read
GET /api/scheduling/public/links/:slug/availabilityscheduling.public.read
GET /api/scheduling/scheduling-linksscheduling.scheduling-link.read
GET /api/scheduling/scheduling-links/:link_idscheduling.scheduling-link.read
POST /api/scheduling/appointmentsscheduling.appointment.write
POST /api/scheduling/appointments/:appointment_id/calendar-pushscheduling.appointment.write
POST /api/scheduling/appointments/:appointment_id/cancelscheduling.appointment.write
POST /api/scheduling/appointments/:appointment_id/confirmscheduling.appointment.write
POST /api/scheduling/appointments/:appointment_id/rebookscheduling.appointment.write
POST /api/scheduling/appointments/:appointment_id/remindersscheduling.appointment.write
POST /api/scheduling/appointments/:appointment_id/reschedulescheduling.appointment.write
POST /api/scheduling/availability-rulesscheduling.availability-rule.write
POST /api/scheduling/calendar-connectionsscheduling.calendar-connection.write
POST /api/scheduling/calendar-connections/:connection_id/syncscheduling.calendar-connection.write
POST /api/scheduling/meeting-typesscheduling.meeting-type.write
POST /api/scheduling/no-show/scanscheduling.no-show.write
POST /api/scheduling/public/appointments/:public_token/cancelscheduling.public.write
POST /api/scheduling/public/appointments/:public_token/confirmscheduling.public.write
POST /api/scheduling/public/links/:slug/bookscheduling.public.write
POST /api/scheduling/reminders/tickscheduling.reminder.write
POST /api/scheduling/scheduling-linksscheduling.scheduling-link.write
GET /api/searchsearch.search.read
GET /api/search/saved-queriessearch.saved-query.read
POST /api/searchsearch.search.write
POST /api/search/indexsearch.index.write
POST /api/search/saved-queriessearch.saved-query.write
GET /api/secrets/:refsecret.secret.read
POST /api/secretssecret.secret.write
POST /api/secrets/:ref/rotatesecret.rotate.write
GET /api/sequences/:sequence_idsequence.sequence.read
GET /api/sequences/guards/logsequence.guard.read
POST /api/sequencessequence.sequence.write
POST /api/sequences/:sequence_id/enrollsequence.enroll.write
POST /api/sequences/:sequence_id/stepssequence.step.write
POST /api/sequences/:sequence_id/triggerssequence.trigger.write
POST /api/sequences/enrollments/:enrollment_id/controlsequence.enrollment.write
POST /api/sequences/guards/checksequence.guard.write
POST /api/sequences/guards/outcomesequence.guard.write
POST /api/sequences/ticksequence.tick.write
POST /api/sequence-templatessequence-template.sequence-template.write
GET /api/service-request/tickets/:ticket_idservice-request.ticket.read
POST /api/service-request/queuesservice-request.queue.write
POST /api/service-request/ticketsservice-request.ticket.write
POST /api/service-request/tickets/:ticket_id/assignservice-request.ticket.write
POST /api/service-request/tickets/:ticket_id/transitionservice-request.ticket.write
GET /api/sla/at-risksla.at-risk.read
GET /api/sla/attainmentsla.attainment.read
GET /api/sla/breach-reasonssla.breach-reason.read
GET /api/sla/breachessla.breache.read
GET /api/sla/breaches/:breach_idsla.breache.read
GET /api/sla/calendarssla.calendar.read
GET /api/sla/calendars/:calendar_idsla.calendar.read
GET /api/sla/clockssla.clock.read
GET /api/sla/clocks/:clock_idsla.clock.read
GET /api/sla/clocks/:clock_id/firingssla.clock.read
GET /api/sla/policiessla.policy.read
GET /api/sla/policies/:policy_idsla.policy.read
GET /api/sla/policies/:policy_id/rungssla.policy.read
PATCH /api/sla/rungs/:rung_idsla.rung.write
POST /api/sla/breach-reasonssla.breach-reason.write
POST /api/sla/breach-scansla.breach-scan.write
POST /api/sla/breaches/:breach_id/recoverysla.breache.write
POST /api/sla/calendarssla.calendar.write
POST /api/sla/clockssla.clock.write
POST /api/sla/clocks/:clock_id/breachsla.clock.write
POST /api/sla/clocks/:clock_id/cancelsla.clock.write
POST /api/sla/clocks/:clock_id/pausesla.clock.write
POST /api/sla/clocks/:clock_id/reassignsla.clock.write
POST /api/sla/clocks/:clock_id/resumesla.clock.write
POST /api/sla/clocks/:clock_id/satisfysla.clock.write
POST /api/sla/clocks/mergesla.clock.write
POST /api/sla/policiessla.policy.write
POST /api/sla/policies/:policy_id/rungssla.policy.write
POST /api/sla/systemic-incidents/open-pendingsla.systemic-incident.write
POST /api/sla/ticksla.tick.write
POST /api/social/handlessocial.handle.write
POST /api/social/interactionssocial.interaction.write
POST /api/social/interactions/:interaction_id/capture-leadsocial.interaction.write
GET /api/source-assertionssource-assertion.source-assertion.read
POST /api/source-assertionssource-assertion.source-assertion.write
POST /api/source-assertions/:assertion_id/supersedesource-assertion.supersede.write
GET /api/source-recordssource-record.source-record.read
GET /api/source-records/:capture_idsource-record.source-record.read
POST /api/source-recordssource-record.source-record.write
POST /api/source-records/:capture_id/crosswalkssource-record.crosswalk.write
POST /api/source-records/:capture_id/normalizesource-record.normalize.write
POST /api/source-records/:capture_id/promotesource-record.promote.write
GET /api/source-rights/attestationssource-right.attestation.read
GET /api/source-rights/attestations/:attestation_idsource-right.attestation.read
GET /api/source-rights/permitted-usesource-right.permitted-use.read
POST /api/source-rights/attestationssource-right.attestation.write
GET /api/storm/overlaystorm.overlay.read
GET /api/taxonomy/extraction-schemastaxonomy.extraction-schema.read
GET /api/taxonomy/healthtaxonomy.health.read
GET /api/taxonomy/prompt-templatestaxonomy.prompt-template.read
POST /api/taxonomy/versions/:taxonomy_version_id/activatetaxonomy.version.write
GET /api/tenants/:tenant_idtenant.tenant.read
GET /api/tenants/:tenant_id/contacttenant.contact.read
POST /api/tenantstenant.tenant.write
POST /api/tenants/:tenant_id/bustenant.bu.write
POST /api/tenants/:tenant_id/fiscal-calendartenant.fiscal-calendar.write
POST /api/tenants/:tenant_id/reseller-attachtenant.reseller-attach.write
POST /api/tenants/:tenant_id/sub-tenantstenant.sub-tenant.write
GET /api/tenant-lifecycle/:tenant_id/statetenant-lifecycle.state.read
POST /api/tenant-lifecycle/:tenant_id/offboardtenant-lifecycle.offboard.write
POST /api/tenant-lifecycle/:tenant_id/reinstatetenant-lifecycle.reinstate.write
POST /api/tenant-lifecycle/:tenant_id/suspendtenant-lifecycle.suspend.write
POST /api/tenant-lifecycle/sandboxtenant-lifecycle.sandbox.write
GET /api/trace/:trace_idtrace.trace.read
GET /api/trace/healthtrace.health.read
POST /api/trace/exportstrace.export.write
POST /api/trace/regression-asserttrace.regression-assert.write
GET /api/userinfouserinfo.userinfo.read
GET /api/vault/healthvault.health.read
POST /api/vault/decryptvault.decrypt.write
POST /api/vault/encryptvault.encrypt.write
POST /api/vault/keysvault.key.write
POST /api/vault/keys/:key_id/rotatevault.key.write
POST /api/vault/keys/:key_id/shredvault.key.write
GET /api/voice/callsvoice.call.read
GET /api/voice/calls/:voice_call_idvoice.call.read
GET /api/voice/tracking-numbersvoice.tracking-number.read
POST /api/voice/callsvoice.call.write
POST /api/voice/tracking-numbersvoice.tracking-number.write
POST /api/voice/tracking-numbers/:tracking_number_id/releasevoice.tracking-number.write
POST /api/voice/webhooks/twilio/recordingvoice.webhook.write
POST /api/voice/webhooks/twilio/statusvoice.webhook.write
GET /api/webhooks/deliverieswebhook.delivery.read
GET /api/webhooks/dlqwebhook.dlq.read
GET /api/webhooks/endpointswebhook.endpoint.read
POST /api/webhooks/deliveries/:delivery_id/replaywebhook.delivery.write
POST /api/webhooks/endpointswebhook.endpoint.write
POST /api/webhooks/endpoints/:endpoint_id/subscribewebhook.endpoint.write
POST /api/webhooks/publishwebhook.publish.write
GET /api/workflows/:run_idworkflow.workflow.read
POST /api/workflows/:run_id/signalworkflow.signal.write
POST /api/workflows/definitionsworkflow.definition.write
POST /api/workflows/startworkflow.start.write

4 · Wire up a development project

Two environment variables and one header. Both a local gateway and the hosted one are supported — the only difference is the base URL and which key you present.

TargetBase URLNotes
Hostedhttps://cloud.projexlight.comUse a pk_live_ key issued against your real tenant
Localhttp://localhost:4000Port from GATEWAY_PORT in the root .env (4000 by default). Issue a pk_test_ key against your dev tenant
# .env of YOUR application — never commit the key
PROJEXCLOUD_GATEWAY_URL=https://cloud.projexlight.com
PROJEXCLOUD_API_KEY=pk_live_…
PROJEXCLOUD_TENANT_ID=<your-tenant-uuid>
PROJEXCLOUD_TIMEOUT_MS=8000

The request contract

POST {PROJEXCLOUD_GATEWAY_URL}/api/sla/clocks
Authorization:    Bearer {PROJEXCLOUD_API_KEY}   ← the key goes HERE
Content-Type:     application/json
Idempotency-Key:  <stable key per logical operation>
x-correlation-id: <uuid>

{ "tenant_id": "…", "policy_id": "…", "subject_ref": "lead:…" }
Three mistakes that cost the most time.

Because handlers read tenant_id from the payload, the gateway cross-checks it against the key's own tenant and answers 403 on a mismatch. A leaked key therefore cannot be aimed at another tenant — but it also means the tenant_id you send must be the one the key was issued for.

Supporting both targets from one codebase

Read the base URL from the environment rather than hard-coding it, keep a separate key per target, and let a run-time override win over the committed default. A shell-provided value beats a .env entry with most dotenv loaders, which makes switching a one-command affair:

PROJEXCLOUD_GATEWAY_URL=http://localhost:4000 npm run dev   # local gateway
PROJEXCLOUD_GATEWAY_URL=https://cloud.projexlight.com npm run dev
Design your client to degrade, not to fail. Treat "no key configured" as a distinct state from "key rejected". If a capability is unreachable, fall back to something safe and record which path produced the result — a wall-clock SLA verdict and a business-calendar one are not the same measurement and must never be averaged together silently.

Two ways to present the credential

Both are supported on every tenant route, and both are checked by the same gate. Pick by volume.

Send the key directly

One header. Nothing to refresh.
curl "$GATEWAY/api/sla/policies?tenant_id=$TENANT"   -H "Authorization: Bearer $PROJEXCLOUD_API_KEY"
  • Nothing to implement beyond a header
  • Revocation takes effect within a second
  • Right for anything up to steady moderate traffic

Exchange it for a token

OAuth2 client_credentials. Verify once, call many times.
curl -sX POST "$GATEWAY/api/auth/token"   -H 'Content-Type: application/json'   -d '{"grant_type":"client_credentials",
       "client_id":"web-backend",
       "client_secret":"'"$PROJEXCLOUD_API_KEY"'"}'
# → { "access_token":"eyJ…", "token_type":"Bearer",
#      "expires_in":900, "scope":"sla.clock.read sla.clock.write" }
  • Credential verification happens once per token, not once per request
  • An optional scope may narrow the token below what the key holds — never widen it
  • Right for high request rates
A token cannot be revoked before it expires, so its lifetime is your revocation delay. It defaults to 15 minutes and is capped at an hour. Revoking the key stops new tokens immediately; tokens already issued run out on their own. If you need revocation to bite instantly, send the key directly.
Invalid, revoked and expired credentials all answer invalid_client with identical wording. That is deliberate — a distinguishable answer would confirm to anyone probing with harvested strings that a particular client exists. Check GET /api/api-keys to see which of your own keys are live.

5 · Rotate and revoke

OperationCallBehaviour
ListGET /api/api-keys?tenant_id=…Returns records with the display prefix only — never the key
RotatePOST /api/api-keys/:key_id/rotate201 with a new plaintext. The old key keeps working for a 24-hour grace window, so you can deploy before revoking
RevokePOST /api/api-keys/:key_id/revokeImmediate, and broadcast over Redis to every gateway replica within a second

Rotation without downtime: rotate → deploy the new key → confirm traffic on the new prefix via last_used_at → revoke the old key. Skipping the final revoke leaves a live credential in the wild after the grace window ends.

Every issuance, rotation and revocation emits an audit event (api-key.issued.v1, api-key.rotated.v1, api-key.revoked.v1), so key custody is reviewable after the fact.

6 · Troubleshooting

SymptomCauseFix
401 Missing bearer tokenNo Authorization header, or the key is in x-api-keySend Authorization: Bearer pk_live_…
401 API key invalid, revoked, or expiredWrong key, revoked, past expires_at, or a live key against a local gateway (each target has its own store)Check with GET /api/api-keys?tenant_id=…; confirm you are pointed at the right gateway
403 …missing required scope(s)Key lacks the derived scopeThe response lists required_scopes and granted_scopes — rotate or re-issue with the missing scope
403 …issued for a different tenantPayload tenant_id ≠ the key's tenantSend the key's own tenant id
404 on a plausible pathA /{sdk}/v1/… style URLUse the SDK's real /api/<domain>/… route from the API reference
Works locally, 401 in cloudDev key used against the hosted gatewayIssue a key per environment

← Developer Hub API reference: api-keys ↗ Agent Cookbook ↗