Developer platform · General API · webhooks · connectors

Make your platform AI-operable.

Call 2,418 named operations through one contract-generated API, with least-privilege scopes, workspace pinning, dry-run validation and replay-safe writes. Subscribe to canonical events through HMAC-signed background webhooks, or build a governed package in the six-stage Connector Builder.

Scoped Bearer keys2,418 contract actionsOpenAPI 3.0.3 + PostmanHMAC webhook delivery
Scoped request in
Signed webhook out → 200 OK
Live Bearer action → OpsIQ → signed webhook POST /api/v1.php Authorization: Bearer { "action": "tickets.reply", "dry_run": true } VERIFY · RUN · AUDIT key + workspace ✓ scope + contract ✓ request traced your webhook endpoint X-OpsIQ-Event: ticket.replied verify HMAC → handle 200 OK
2,418named General API actions
69least-privilege scopes
dry_runvalidate write contracts before execution
24hsuccessful idempotent-write replay
The request lifecycle

Scoped request in. Signed event out.

POST an action to /api/v1.php with a scoped Bearer key. OpsIQ pins the workspace, validates the action contract and permissions, then executes or dry-runs it. Subscribed canonical events leave through an HMAC-signed delivery queue with traceable attempts and bounded retries.

HMAC-SHA256 webhooks - inbound and outbound webhook payloads are signed over the raw body; API calls authenticate with a scoped Bearer key.
Automatic retries - failed deliveries back off and retry, with a delivery ID you can trace.
Replay-safe writes - successful idempotent writes return the same response for 24 hours; concurrent duplicates share one claim.
Your app POST /api/v1.php Authorization: Bearer idempotency_key: idem_31c7 VERIFY - RUN - AUDIT Your endpoint POST /webhooks verify HMAC 200 OK key + workspace pinned scope + role checked contract validated execute or dry-run SIGNED WEBHOOK DELIVERY { X-OpsIQ-Event: ticket.replied X-OpsIQ-Delivery: dlv_8f2a9 X-OpsIQ-Timestamp: 1786584362 X-OpsIQ-Signature: 9c4e0a7b... } delivered
01 Bearer request 02 pin workspace 03 validate contract 04 execute or dry-run 05 queue signed event
Quickstart

From key to governed integration in three steps.

Issue a least-privilege key, discover the generated contract, dry-run a write, then subscribe a webhook when your application needs background event delivery.

Contract consoleConnected
GENERAL API · contract driven 1 · Generate key opq_live_xxx · one scoped Bearer key 2 · Discover contracts POST /api/v1.php · meta.actions 3 · Validate a write tickets.reply · dry_run: true 4 · Subscribe to events ticket.replied · HMAC delivery
Executedry_run → approved write, same contract
01

Get a key

Sign up and generate a scoped Bearer API key (opq_…) in developer settings, and give each integration only the surfaces it needs.

02

Discover & validate

Read meta.actions, meta.action and meta.scopes; use dry_run to validate writes before execution.

03

Execute & subscribe

Add an idempotency key for writes, then subscribe your endpoint to the canonical events your application needs.

How it fits together

One connector pattern. Five clean primitives.

Anything platform-specific lives in a connector. The OpsIQ core stays generic, the AI stays predictable, and your integration stays auditable.

01
Triggers

Tell OpsIQ what just happened.

Emit canonical events from your platform, or define a governed custom event. Subscribers receive the event through the registered priority and delivery rules.

Event reference
invoice.paid canonical event AI brainlive context rulespriority 20 mirrorwrite first webhooksigned
Universal + custom eventsinvoice.paid, ticket.created, subscription.cancelled, customer.signed_up or your own. Fan-out subscribersThe AI brain, automation rules, mirror connectors and your webhook endpoints all react. Priority orderDeterministic dispatch so mirrors write before alerts fire.
02 / Action contracts

Tell OpsIQ what the AI is allowed to do.

Every named operation receives a generated contract: scope, feature, read or write kind, risk, confirmation policy, request and response schema, dry-run support, idempotency and stable errors.

Action schema
The AI can't invent actionsIt can only ask to run ones you've registered. Confirmation policyRisky actions trigger a preview card before any side-effect. Role + surface gatingEvery action declares the roles and surfaces allowed to run it.
generated action contract · JSON
{
  "action": "tickets.reply",
  "kind": "write",
  "scopes": ["tickets.write", "admin"],
  "feature": "ticket_system",
  "risk": "medium",
  "confirmation": "recommended",
  "idempotency": "supported",
  "dry_run": true,
  "request": { "type": "object" },
  "response": { "required": ["success"] }
}
03 / Signed webhooks

Push events to your stack - with cryptographic proof.

Subscribe any URL to any of the 52 deliverable events. OpsIQ POSTs the JSON payload signed with HMAC-SHA256 over the scheme version, the timestamp, the delivery ID and the body together, not the body alone, so yesterday's delivery cannot be replayed at you today.

Replay protectionTraceable delivery ID5-step back-off
Webhook reference
verify webhook - php
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_OPSIQ_SIGNATURE'] ?? '';   // "v2=<hex>"
$ts  = (int)($_SERVER['HTTP_X_OPSIQ_TIMESTAMP'] ?? 0);
$id  = (int)($_SERVER['HTTP_X_OPSIQ_DELIVERY'] ?? 0);

if (abs(time() - $ts) > 300) http_response_code(401);   // replay window
[$ver, $hex] = array_pad(explode('=', $sig, 2), 2, '');  // split the prefix

$signed   = $ver . '.' . $ts . '.' . $id . '.' . $raw;   // NOT the body alone
$expected = hash_hmac('sha256', $signed, $secret);
if (!hash_equals($expected, $hex)) http_response_code(401);

$event = json_decode($raw, true);   // $id is stable across retries
04 / SDKs

Use direct HTTP or generate the client you prefer.

The full General API is published as OpenAPI 3.0.3 and Postman. PHP reference senders plus Node and Python integration clients cover signed event and webhook flows; any stack can call the same JSON action surface directly.

client examples
const response = await fetch(`${base}/api/v1.php`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.OPSIQ_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    action: "tickets.reply",
    dry_run: true,
    idempotency_key: crypto.randomUUID(),
    body: "Thanks. We are checking this now."
  })
});
$payload = json_encode([
  'action' => 'tickets.reply',
  'dry_run' => true,
  'idempotency_key' => bin2hex(random_bytes(16)),
  'body' => 'Thanks - we are checking this now.',
]);

$ch = curl_init($base . '/api/v1.php');
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => $payload,
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer ' . getenv('OPSIQ_KEY'),
    'Content-Type: application/json',
  ],
]);
response = requests.post(
    f"{base}/api/v1.php",
    headers={
        "Authorization": f"Bearer {os.environ['OPSIQ_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "action": "tickets.reply",
        "dry_run": True,
        "idempotency_key": str(uuid.uuid4()),
        "body": "Thanks - we are checking this now.",
    },
)
Direct JSON HTTPOpenAPI 3.0.3Postman collectionIntegration helpers
05 / The connector pattern

Build once. Plug into anything.

A connector is a governed package generated or imported through Identity, Auth, Test, Actions, Triggers and Review. Its manifest declares the capability surfaces OpsIQ is allowed to expose.

Open the six-stage Builder
Six guided stagesIdentity, Auth, Test, Actions, Triggers and Review keep package generation explainable. Contract-driven8 profiles and 57 capability types declare the surfaces a package implements. Release-gatedSafety, provenance and conformance checks run before a package is treated as ready.
From intent to safe action

Plain English in. Audited operation out.

OpsIQ never invents the right call. It walks through the registered contracts, prepares the payload, asks for confirmation when the action requires it, and produces a complete audit row when it executes, so an AI that can act never becomes an AI you can't trust.

Contract-bound. The AI only proposes actions you've registered.
Confirm before side-effects. Risky actions surface a preview card first.
Total recall. Every prompt, response and action result captured in AI History.
4 stepsintent → audit
HMACsigned before execute
"refund Adam's last invoice" Step 1 · intent resolution → matched: saas.refund_invoice surface: admin · role: owner · Step 2 validate Step 3 · Confirm & sign preview card · invoice #8421 Confirm Step 4 · Audit row actor · prompt · result · 412ms Owner-only · exportable as CSV
No invented callscontract registry only
Confirm-gatedpreview before side-effects
Request traceactor + contract + result
Reference

API surface at a glance.

One JSON POST surface exposes 2,418 named actions. Each generated contract declares auth scopes, read/write kind, risk, confirmation, request schema, dry-run, idempotency and stable errors.

OpenAPI 3.0.3 reference
ActionAuthWrite safetyLimit
Write actionsPOST /api/v1.php · contract validated
ACTION tickets.replytickets.writedry_run + idempotencyConfigured / hour
ACTION security.reportsecurity.writedry_run + idempotencyConfigured / hour
ACTION platform.routes.writeadminconfirmation + keyConfigured / hour
Read & discovery actionsSame POST surface · stable envelopes
ACTION meta.actionsPublic discoveryRead onlyConfigured / hour
ACTION tickets.listtickets.readRead onlyConfigured / hour
ACTION connectors.listconnectors.readRead onlyConfigured / hour
PeopleOS HR actionsCurated hr.* contracts · hr.read / hr.write scopes
ACTION hr.people.listhr.readRead onlyConfigured / hour
ACTION hr.people.createhr.writedry_run + idempotencyConfigured / hour
ACTION hr.org.treehr.readRead onlyConfigured / hour
PeopleOS HR does not send outbound HR webhook events. Reach it through the same POST surface, or subscribe to the events other modules publish. Full reference: PeopleOS API and connectors and the PeopleOS section of the developer reference. The module is entitlement gated, so a key without hr gets a refusal, not an empty list. What PeopleOS HR is.

Rate limits are configured per key on an hourly window. Responses expose X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a rejected request returns 429 with Retry-After. Use the OpenAPI 3.0.3 contract or Postman collection generated from the same runtime registries.

Build a connector

Build, test and release in six stages.

Start from a governed profile or define the contract yourself. The Builder validates each stage before it generates a self-contained connector package.

1Identity. Name, slug, profile and declared capability surfaces.
2Auth. API key, Bearer, Basic, OAuth 2.0, HMAC or a custom client.
3Test. Validate the bounded connection settings before packaging.
4Actions. Map named operations, parameters, scopes and risk.
5Triggers. Declare inbound events, webhooks, polling and sync behavior.
6Review. Inspect the generated contract and pass the release gate.
Test before you ship

Validate safely, then pass the release gate.

Use dry_run on supported write actions, run the Builder's bounded connection test, and inspect the generated manifest and contract before release. Package validation checks capability names, settings, provenance and required files; background workers handle reactive polling and outbound delivery.

Dry-run writes Bounded connection test Contract conformance Cron-driven delivery
connector.php · php
class AcmeConnector extends AbstractConnector { public function slug(): string { return 'acme'; } public function name(): string { return 'Acme'; } public function description(): string { return 'Acme bridge'; } // Use names from ConnectorContract public function capabilities(): array { return ['actions', 'webhook']; } public function settingsSchema(): array { return [['key' => 'token', 'type' => 'password']]; } public function testConnection(array $settings): array { return ['success' => true]; } public function handleWebhook( array $payload, array $headers, array $settings ): array { return ['success' => true]; } }
01 · Connector contract 2.4

A package cannot claim a capability its code does not have.

OpsIQ\Connectors\ConnectorContract is the authority. Runtime discovery, the Builder, first-party packages, conformance, the General API and every reference read that one registry. Declare a capability and three files have to agree with each other before OpsIQ routes a single row to you.

FIG. 01connector package, parts manifest scale 1:1 · contract 2.4
  1. 01 connector.php the runtime class
  2. 02 IdentityProvider.php stable external key
  3. 03 SalesIngest.php reconcileSales()
  4. 04 actions.json named operations
  5. 05 settings.json operator configuration
  6. 06 connector_contract.json machine-readable authority
07 signature.json one HMAC over a SHA-256 of every file above, plus an ed25519 proof of origin
Capability matrix all 57, as registered
actions triggers webhook journey_events context guest_lookup identity sales sales_reconciliation billing payments commerce inbound_ticket outbound_reply ticket_merge sync_users sync_departments sync_orders sync_products sync_projects customer_import security_events security_native_block survey reply_feedback promo_audience promo_feed email_mailbox inbound_email outbound_email mailbox ai_tickets ai_reply ai_brain_aware native_api platform_bridge native_data crm block_registry inbound_comment outbound_note outbound_notification oauth reviews local seo site_intelligence analytics comms admin_assets enterprise_hooks durable_queue ticket_mirror attachment_bridge database_migrations scheduled_workers conflict_resolution
generated 30 declarative 20 extension 7
Two signatures, two questions.

hmac-sha256 over a deterministic payload of per-file SHA-256 hashes answers has this been modified since signing. An ed25519 signature answers was it signed by Nabtech, checkable with the bundled public key and no shared secret. Edit one byte and the first stops matching.

marketplace.json is outside the signature by definition: the installer writes it after signing, so the publisher could never have signed it.
02 · tools/mutation_proof.php

A guard that has never failed is unproven.

Green tests tell you nothing broke today. They do not tell you the guard you wrote would stop the thing it was written to stop, because a guard that is never exercised passes exactly like a guard that does nothing. So we stop its heart on purpose, and demand that the monitor screams.

guard · read role must refuse a destructive operation CH1 verify_commerce_contracts
src/Connectors/CommerceRoleMapVerifier.php MUTATED
211    /* A read role must never reach a destructive operation. */
212-   if ($isRead && $isDestructive) {
212+   if (false) {
213        $errors[] = "read role '{$role}' points at a destructive op";
214        return false;
215    }
Exactly one textual match is required. Zero matches, or two, and the proof stops before it can lie.
$ php tools/mutation_proof.php --spec=proofs.json
1[ ok ]hash    sha256 4f2a9c1e…
          remember the file exactly as it is
2[ ok ]control exit 0
          green BEFORE anything is touched
3[ ok ]mutate  1 match, lint ok
          one replacement, then php -l the mutant
4[RED ]prove   exit 1
          and "read role destructive" IS in the failures
5[ ok ]restore bytes written
          the original, not a regenerated copy
6[ ok ]verify  sha256 matches
          and the check is green again
 ══ PROVEN. file restored, hash matched, 1 proof run, 0 failed.
A failed proof is never recorded. Evidence that a check went wrong is not evidence that a guard works.

The mutation never applied.

The search text did not match, the file was untouched, the suite passed, and the proof recorded green under the old code about code that was never old. Caught by requiring exactly one textual match and comparing file hashes either side.

The mutation broke something else.

The suite went red because the file stopped parsing or the bootstrap died, and the proof recorded a red that had nothing to do with the guard. Caught by linting the mutant and requiring the named test among the failures.

It cannot leave a mutant on disk.

Every mutation copies the original to a sidecar outside the web-served tree, writes a marker naming the outstanding change, and is undone by a shutdown handler on every exit path. A marker from a crashed run is honoured before the next mutation of that file.

03 · Conformance

Certification is a command with an exit code.

Not a review queue where somebody reads your code and forms an opinion. Run the same suites we run, on your own machine, before you submit. 8 published JSON Schemas under doc/contracts/schemas/ cover settings, actions, Builder specs, journey and commerce event mappings, workflow recipes, connector contracts and release manifests.

  1. manifests parse and agree with each other
  2. capabilities exist on the runtime class
  3. interfaces implemented where declared
  4. files every required file present
  5. actions match the actions manifest
  6. destructive declare confirmation
  7. identity a stable external key, not email
  8. replay a repeat updates, never duplicates
  9. enterprise declarations match the extension
  10. docs README, DEVELOPER, CHANGELOG
  11. schemas every JSON validates
exit 0 acme: conformant against contract 2.4 A failing suite names the check and the file. You fix it, not us.
$ php tools/test_connector_conformance_all.php --connector=acme $ php tools/test_connector_json_schemas.php $ php tools/verify_commerce_contracts.php $ php tools/release_gate_connectors.php --require-signatures

Point the first one at a real non-production account with --live-settings=/secure/test-acme.json. While you are still iterating, swap the last one for --unsigned-ok: it treats a missing signature as an expected warning rather than pretending it is fine.

04 · commerce_roles.json

Declaring what you can sell is a file, not a registration.

A connector that can sell declares which of its own operations answer OpsIQ's canonical commerce roles. The presence of the file is the claim. It appears in the selling settings the moment it declares and disappears when it stops. There is no list to join and nobody to ask.

canonical role commerce_roles.json your operation
catalog_search READ acme_search_products ok order_get READ acme_get_order ok order_create WRITE acme_create_order recovery: acme_get_order order_create WRITE db_insert_record generic record write, no recovery
REFUSED

A canonical write role mapped to a generic record write. The match is structural: a storage noun beside a mutating verb, whatever your naming convention. Writing the business row direct skips the platform's pricing, availability locking and confirmation mail, and produces an order the merchant's own system only half believes in. No recovery either, so a timeout would be unrecoverable.

commerce_roles.jsonVALID
{
  "commerce": {
    "roles": {
      "catalog_search": { "operation": "acme_search_products", "transport": "http" },
      "order_get":      { "operation": "acme_get_order",       "transport": "http" },
      "order_create":   { "operation": "acme_create_order",    "transport": "http",
                         "recovery":  "acme_get_order" }
    }
  }
}
Its own file on purpose: connector_contract.json is generated and its schema sets additionalProperties:false, so a hand-authored block there would fail validation and then be overwritten on the next generator run.
01
Closed vocabulary

A role not in CommerceRole does not exist. OpsIQ never infers one from an operation name, because a name is not a promise.

02
Your own action

The operation must appear in your actions.json. Declaring one that does not exist promises the customer something that then produces nothing.

03
Writes declare recovery

The most dangerous timeout is the one after the platform may already have done the work. Without an idempotent "did my write land" lookup, the only options are retry blind or lose the order.

04
Payment roles certify

Any payment-class role also declares payment_profile with the environment, webhook_verify using the provider's own scheme, and event_map. An unstated environment is how a test-mode provider takes a live payment.

The Builder validates these as you type and the verifier validates the shipped package. Both call the same functions, so they cannot disagree.
⚖️ How it compares

OpsIQ vs a DIY integration.

What a contract-bound, signed, audited platform gives you that rolling your own webhooks and AI-action plumbing never will.

CapabilityRoll your ownOpsIQ
Scoped Bearer API + HMAC-signed webhooks Hand-rolled
Dry-run writes + 24-hour successful replay DIY
1m → 5m → 30m → 2h → 12h retries DIY queue
Action-contract registry (AI can't invent calls)
Confirmation policy before side-effects
Stable error envelope + request IDs Manual logging
Direct HTTP + generated client options Write your own
OpenAPI 3.0.3 machine-readable contract Maybe
Six-stage Builder + package release gate Build a toolchain
Workspace-pinned keys and hourly limits Varies
Connector pattern: platform code stays isolated
FAQ

Developer questions, answered.

Authentication, contracts, retries, client generation, safe testing and the connector model, before the first production request.

General API calls send Authorization: Bearer opq_…. Keys can be unrestricted, restricted or read-only, carry least-privilege scopes, obey an hourly limit and can be pinned to a workspace. Webhook signing is separate: outbound deliveries carry X-OpsIQ-Signature, X-OpsIQ-Timestamp, X-OpsIQ-Event and X-OpsIQ-Delivery.
No. The AI can only propose named operations in the action registry. Each generated contract declares scope, request fields, risk and confirmation policy; operations whose policy requires confirmation surface a human preview before execution.
Recompute hash_hmac('sha256', $rawBody, $secret) and compare its hexadecimal digest with X-OpsIQ-Signature using hash_equals. Validate X-OpsIQ-Timestamp within your replay window and use X-OpsIQ-Delivery as the traceable delivery identifier.
A non-successful attempt enters the background delivery queue. OpsIQ retries on the 1-minute, 5-minute, 30-minute, 2-hour and 12-hour ladder while retaining the delivery identifier and attempt history for diagnostics.
Call the JSON action surface directly, import the generated Postman collection, or generate a client from OpenAPI 3.0.3. PHP reference senders and Node/Python integration clients support event and webhook flows; they are helpers rather than separate API contracts.
Use dry_run on actions whose generated contract supports it, run the Connector Builder's bounded connection test, inspect its generated files, and pass the package conformance and release gates before treating the connector as ready.
The installed release generates its API and connector contracts from its own runtime registries. Your deployment owns its public URL, secrets, storage and cron/workers, so behavior should be verified against the installed release rather than assumed from a cloud environment.
A connector is a governed package containing the adapter, settings.json, optional actions.json, a generated capability contract, documentation and optional enterprise extensions. The six-stage Builder assembles it; the registry discovers it; conformance and release gates validate it.