Skip to content

The local evaluator is the read path — it tells you what should happen. The platform at api.tenor.run is the write path — it makes it happen. It loads entity state from Postgres, verifies preconditions under snapshot isolation, executes flows atomically, and records provenance for every decision.

This guide walks through deploying the approval.tenor contract from the previous tutorials to the hosted platform.

Create an Organization

Sign up at app.tenor.run or create an organization via the management API:

bash
curl -s -X POST https://api.tenor.run/api/v1/organizations \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-org",
    "display_name": "My Organization"
  }' | jq .
json
{
  "id": "org_01HWQZ3K5XMJN8V2P4G6",
  "name": "my-org",
  "display_name": "My Organization",
  "created_at": "2026-03-01T12:00:00Z",
  "plan": "free"
}

Generate an API Key

Create an API key with deployment and execution permissions:

bash
curl -s -X POST https://api.tenor.run/api/v1/organizations/org_01HWQZ3K5XMJN8V2P4G6/api-keys \
  -H "Content-Type: application/json" \
  -d '{
    "name": "deploy-key",
    "permissions": ["deploy", "evaluate", "execute"],
    "admin": false
  }' | jq .
json
{
  "id": "key_01HWQZ4M7YPKR9W3Q5H8",
  "name": "deploy-key",
  "token": "tk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "permissions": ["deploy", "evaluate", "execute"],
  "admin": false,
  "created_at": "2026-03-01T12:00:30Z"
}

Save the token value. It is displayed only once. Set it as an environment variable for the CLI:

bash
export TENOR_PLATFORM_TOKEN=tk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Deploy the Contract

Deploy with the CLI. The command elaborates the contract, runs static analysis, and uploads the interchange bundle to the platform in a single step:

bash
tenor deploy approval.tenor --org my-org
$ tenor deploy approval.tenor --org my-org

  ✓ Elaborated (6 passes, 0 errors)
  ✓ Static analysis passed (S1–S8)
  ✓ Deployed to api.tenor.run/my-org/approval

  Endpoints:
    POST  /my-org/approval/evaluate           — evaluate facts, get verdicts
    GET   /my-org/approval/actions             — action space per persona
    POST  /my-org/approval/flows/{id}/execute  — execute flow atomically
    POST  /my-org/approval/simulate            — dry-run flow execution
    GET   /my-org/approval/.well-known/tenor   — contract manifest

The platform will not accept a contract that fails static analysis. If any of S1-S8 fail, the deploy is rejected with specific error messages. This is not a deployment check — it is the same structural verification the evaluator runs locally.

Verify the Deployment

Retrieve the contract manifest to confirm the deployment is live:

bash
curl -s https://api.tenor.run/my-org/approval/.well-known/tenor \
  -H "Authorization: Bearer tk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" | jq .
json
{
  "contract_id": "approval",
  "org": "my-org",
  "version": "1",
  "deployed_at": "2026-03-01T12:01:15Z",
  "etag": "W/\"sha256:a3f8c9d2...\"",
  "entities": ["Document"],
  "personas": ["admin", "operator"],
  "flows": ["approval_flow"],
  "operations": ["approve_document", "reject_document"],
  "static_analysis": {
    "s1_state_space": "1 entity, 3 states",
    "s2_reachability": "3/3 reachable",
    "s3_admissibility": "2 operations, all satisfiable",
    "s4_authority": "2 personas, 2 entries",
    "s5_verdicts": "1 type from 1 rule",
    "s6_flow_paths": "1 flow, 2 paths",
    "s7_complexity": "depth 1/1",
    "s8_verdict_unique": "all unique"
  }
}

The manifest includes the full static analysis summary. Any client can verify what the deployed contract guarantees before interacting with it.

Remote Evaluation

Evaluate facts against the deployed contract. This is the same evaluation that runs locally, but executed on the platform with the contract already loaded:

bash
curl -s -X POST https://api.tenor.run/my-org/approval/evaluate \
  -H "Authorization: Bearer tk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -H "Content-Type: application/json" \
  -d '{
    "facts": {
      "review_passed": true
    },
    "entity_states": {
      "Document": "draft"
    }
  }' | jq .
json
{
  "verdicts": [
    {
      "name": "review_ok",
      "rule": "check_review",
      "stratum": 0,
      "payload": { "type": "Bool", "value": true },
      "provenance": {
        "facts_used": ["review_passed"],
        "fact_values": { "review_passed": true },
        "timestamp": "2026-03-01T12:05:00Z"
      }
    }
  ],
  "entity_states": {
    "Document": "draft"
  },
  "evaluation_metadata": {
    "rules_evaluated": 1,
    "rules_fired": 1,
    "strata_evaluated": 1,
    "timestamp": "2026-03-01T12:05:00Z"
  }
}

The response is identical in structure to local evaluation. SDKs and integrations can switch between local and remote evaluation without changing their parsing logic.

Remote Action Space

Query what the operator can do right now:

bash
curl -s "https://api.tenor.run/my-org/approval/actions?persona=operator" \
  -H "Authorization: Bearer tk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -H "X-Facts: {\"review_passed\": true}" \
  -H "X-Entity-States: {\"Document\": \"draft\"}" | jq .
json
{
  "persona": "operator",
  "available_actions": [
    {
      "operation": "approve_document",
      "effects": [
        { "entity": "Document", "from": "draft", "to": "approved" }
      ],
      "satisfied_preconditions": ["verdict_present(review_ok)"],
      "flows": ["approval_flow"]
    }
  ],
  "blocked_actions": [],
  "active_verdicts": [
    {
      "name": "review_ok",
      "rule": "check_review",
      "stratum": 0
    }
  ]
}

The action space on the platform is functionally identical to local evaluation. The difference is what happens next: locally, the action space is advisory. On the platform, you can execute the action and the platform enforces atomicity, records provenance, and persists state transitions.

Execute a Flow

This is where the platform diverges from the local evaluator. Execute the approval_flow atomically — entity state transitions are applied against Postgres with snapshot isolation, and every decision is recorded in a provenance chain:

bash
curl -s -X POST https://api.tenor.run/my-org/approval/flows/approval_flow/execute \
  -H "Authorization: Bearer tk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -H "Content-Type: application/json" \
  -d '{
    "persona": "operator",
    "facts": {
      "review_passed": true
    },
    "entity_states": {
      "Document": "draft"
    }
  }' | jq .
json
{
  "flow_id": "approval_flow",
  "outcome": "success",
  "execution_id": "exec_01HWQZ8N9BWLP2X4R6T0",
  "steps": [
    {
      "step_id": "step_approve",
      "type": "OperationStep",
      "operation": "approve_document",
      "persona": "operator",
      "outcome": "approved",
      "effects_applied": [
        {
          "entity": "Document",
          "instance": "_default",
          "from": "draft",
          "to": "approved"
        }
      ]
    }
  ],
  "entity_states_after": {
    "Document": { "_default": "approved" }
  },
  "provenance": {
    "execution_id": "exec_01HWQZ8N9BWLP2X4R6T0",
    "flow": "approval_flow",
    "persona": "operator",
    "timestamp": "2026-03-01T12:10:00Z",
    "snapshot": {
      "type": "at_initiation",
      "facts": { "review_passed": true },
      "verdicts": [
        {
          "name": "review_ok",
          "rule": "check_review",
          "stratum": 0,
          "payload": { "type": "Bool", "value": true }
        }
      ]
    },
    "derivation": [
      {
        "operation": "approve_document",
        "persona": "operator",
        "precondition": "verdict_present(review_ok)",
        "precondition_satisfied": true,
        "effect": "Document._default: draft -> approved",
        "verdict_chain": [
          {
            "verdict": "review_ok",
            "rule": "check_review",
            "stratum": 0,
            "facts_used": {
              "review_passed": {
                "value": true,
                "source": "review_service",
                "protocol": "http"
              }
            }
          }
        ]
      }
    ]
  }
}

The response includes:

  • Outcome — the terminal result of the flow (success or failure)
  • Steps executed — every step with its operation, persona, outcome, and effects
  • Entity states after — the new state of every entity instance affected
  • Provenance — the complete derivation chain from operation back to source facts

The provenance chain is not a log entry. It is a structured, queryable record that traces the authorization of every state transition back to the facts and verdicts that justified it. When someone asks "who authorized this and why?", the answer is this response.

Atomic Execution Guarantees

Flow execution on the platform provides guarantees that local evaluation cannot:

All-or-nothing execution. Every entity state transition in a flow either succeeds together or none commit. There is no partial execution and no inconsistent state. This is enforced at the Postgres transaction level.

Snapshot isolation. Facts and verdicts are captured at flow initiation and frozen for the duration of execution (the snapshot: at_initiation semantics). No TOCTOU races between evaluation and effect application.

Optimistic concurrency control. If another execution modifies an entity between your snapshot and your commit, the execution is rejected and must be retried. No silent data races.

Provenance recording. Every execution, every step, every state transition is recorded with a complete derivation chain. Provenance is stored in Postgres alongside entity state, queryable and exportable.

These are the executor obligations (E1-E20) from the Tenor specification. The platform implements all twenty.

Dry-Run with Simulate

Before executing for real, you can simulate the flow on the platform. The simulate endpoint runs the same execution logic but does not commit state changes:

bash
curl -s -X POST https://api.tenor.run/my-org/approval/simulate \
  -H "Authorization: Bearer tk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -H "Content-Type: application/json" \
  -d '{
    "persona": "operator",
    "flow_id": "approval_flow",
    "facts": {
      "review_passed": true
    },
    "entity_states": {
      "Document": "draft"
    }
  }' | jq .

The response has the same structure as the execute response — same provenance, same effects — but with "dry_run": true. Nothing is persisted.

Persona-to-Identity Mapping

In local evaluation, you pass the persona name directly. In production, the platform maps API keys to personas so that callers authenticate as their assigned role:

bash
curl -s -X POST https://api.tenor.run/api/v1/organizations/org_01HWQZ3K5XMJN8V2P4G6/persona-mappings \
  -H "Authorization: Bearer tk_live_admin_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "contract": "approval",
    "mappings": [
      {
        "api_key_id": "key_operator_01",
        "persona": "operator"
      },
      {
        "api_key_id": "key_admin_01",
        "persona": "admin"
      }
    ]
  }' | jq .

Once mappings are configured, the ?persona= parameter is no longer required — the platform determines the persona from the API key used to authenticate the request. This is executor obligation E15 and E16: instance identity is stable and determined by the executor, not the caller.

Redeploying

When you update the contract, redeploy with the same command:

bash
tenor deploy approval.tenor --org my-org

The platform computes a structural diff between the deployed version and the new version. If the change is non-breaking, the new version is deployed immediately. If the change is breaking, the CLI reports what changed and asks for confirmation.

For full migration control, use tenor diff and tenor migrate:

bash
# See what changed
tenor diff approval-v1.tenor approval-v2.tenor

# Generate and execute a migration plan
tenor migrate approval-v1.tenor approval-v2.tenor --org my-org

See the migration guide for details on breaking change classification, in-flight flow policies, and rollback.

What You Have Now

Your contract is deployed to the platform with:

  • A production API at api.tenor.run/my-org/approval with evaluation, action space, and execution endpoints
  • Atomic flow execution backed by Postgres with snapshot isolation and optimistic concurrency control
  • Complete provenance for every execution, traceable from operation to verdict to fact to source
  • Persona-to-identity mapping connecting API keys to contract roles
  • Static analysis guarantees verified at deploy time — the platform will not serve a contract that fails any of S1-S8

The local evaluator and the platform produce identical verdicts and action spaces. The difference is the write path: the platform makes decisions real, stores state, and records proof.

Next Steps