{
  "$schema": "https://docs.motorical.com/schemas/onboarding-sandbox-journey.schema.json",
  "id": "motorical-developer-sandbox-onboarding",
  "title": "Motorical developer sandbox onboarding journey",
  "version": "1.0.5",
  "updated": "2026-07-23",
  "audience": "AI agents and CLI users onboarding unpaid *.sandbox.motorical.com",
  "canonicalDiscovery": [
    "https://docs.motorical.com/llms.txt",
    "https://docs.motorical.com/llms-full.txt",
    "https://docs.motorical.com/openapi.json",
    "https://api.motorical.com/api/public/openapi.json"
  ],
  "install": {
    "cli": {
      "package": "@motorical/cli",
      "version": "1.0.5",
      "npmPublished": true,
      "npmNamedInstallWorks": true,
      "source": "https://github.com/motorical-smtp/motorical-packages/tree/main/packages/motorical-cli",
      "commands": [
        "npm install -g @motorical/cli",
        "motorical help"
      ],
      "fallbackTarball": "https://registry.npmjs.org/@motorical/cli/-/cli-1.0.5.tgz"
    },
    "mcp": {
      "package": "@motorical/mcp",
      "version": "1.0.5",
      "npmPublished": true,
      "npmNamedInstallWorks": true,
      "source": "https://github.com/motorical-smtp/motorical-packages/tree/main/packages/motorical-mcp",
      "commands": [
        "npm install -g @motorical/mcp"
      ],
      "desiredCommand": "npm install -g @motorical/mcp",
      "fallbackTarball": "https://registry.npmjs.org/@motorical/mcp/-/mcp-1.0.5.tgz",
      "cursorConfig": "https://docs.motorical.com/motorical-mcp.cursor.json"
    }
  },
  "authLifecycle": {
    "canonicalDoc": "https://docs.motorical.com/api-reference/authentication",
    "credentials": [
      {
        "id": "dashboard_jwt",
        "header": "Authorization: Bearer <accessJwt>",
        "ttl": "15m access + 7d refreshToken",
        "obtain": [
          "POST /api/auth/set-password",
          "POST /api/auth/login",
          "POST /api/auth/refresh",
          "CLI motorical refresh"
        ],
        "useFor": [
          "/api/auth/*",
          "/api/domains*",
          "/api/developer/sandbox*",
          "POST /api/domains/{id}/check-dns",
          "POST /api/domains/{id}/verify",
          "POST /api/auth/web-handoff"
        ],
        "notFor": [
          "POST /v1/send",
          "GET /api/public/v1/motor-blocks/{id}/domain-health"
        ]
      },
      {
        "id": "mk_live",
        "header": "Authorization: ApiKey mk_live_\u2026 (or X-Api-Key)",
        "obtain": [
          "sandbox provision credentials.mkApiKey",
          "Motor Block API keys UI"
        ],
        "useFor": [
          "POST /v1/send"
        ],
        "notFor": [
          "domain ops",
          "sandbox APIs",
          "minting public tokens"
        ]
      },
      {
        "id": "ak_live_public_bearer",
        "header": "Authorization: Bearer <minted>",
        "obtain": [
          "POST /api/public/token/account-key with ak_live_"
        ],
        "useFor": [
          "/api/public/v1/* including domain-health"
        ],
        "notFor": [
          "POST /v1/send",
          "dashboard domain verify/check-dns (use dashboard JWT instead)"
        ]
      }
    ],
    "accessJwt": {
      "ttl": "15m",
      "header": "Authorization: Bearer <token>",
      "issuedBy": [
        "POST /api/auth/set-password",
        "POST /api/auth/login",
        "POST /api/auth/refresh",
        "POST /api/auth/web-handoff/exchange"
      ]
    },
    "refreshToken": {
      "ttl": "7d",
      "storage": "CLI stores in ~/.config/motorical/config.json; browsers use __Host-motorical_refresh cookie",
      "nonInteractiveRefresh": "POST /api/auth/refresh with JSON body { refreshToken }",
      "cliCommand": "motorical refresh"
    },
    "sendAuth": {
      "note": "Dashboard JWT is NOT used for POST /v1/send. Use mk_live_ ApiKey from sandbox provision. Optional fromName for display name (not a From header)."
    }
  },
  "steps": [
    {
      "id": "A",
      "name": "Discover",
      "actor": "agent",
      "actions": [
        "GET https://docs.motorical.com/llms.txt",
        "GET https://docs.motorical.com/onboarding-sandbox-journey.json",
        "GET https://docs.motorical.com/openapi.json (Onboarding Auth + Developer Sandbox tags)"
      ]
    },
    {
      "id": "B",
      "name": "Install CLI",
      "actor": "agent",
      "actions": [
        "npm install -g https://docs.motorical.com/motorical-cli-1.0.1.tgz"
      ]
    },
    {
      "id": "C",
      "name": "Signup + verify",
      "actor": "agent",
      "humanGated": [
        "retrieve 6-digit email code"
      ],
      "actions": [
        "motorical signup <email>",
        "POST /api/auth/register",
        "motorical verify <code>",
        "POST /api/auth/verify-code then POST /api/auth/set-password { channel: cli|agent }",
        "Store token + refreshToken from response"
      ]
    },
    {
      "id": "D",
      "name": "Provision sandbox",
      "actor": "agent",
      "actions": [
        "motorical sandbox provision [--handle name]",
        "POST /api/developer/sandbox/provision",
        "Save mk_live_ (returned once), sandbox domain, SMTP creds"
      ]
    },
    {
      "id": "E",
      "name": "Dry-run / allowlisted send",
      "actor": "agent",
      "actions": [
        "motorical send --to <allowlisted> --from noreply@<handle>.sandbox.motorical.com",
        "POST /v1/send with Authorization: ApiKey mk_live_... and dryRun:true (default)"
      ],
      "notes": [
        "Outbound locked to allowlist while developer_sandbox is set",
        "dryRun always allowed"
      ]
    },
    {
      "id": "F",
      "name": "Browser password (optional for send, required for dashboard)",
      "actor": "human",
      "agentCanMint": true,
      "actions": [
        "motorical open  OR  MCP motorical_web_handoff",
        "POST /api/auth/web-handoff \u2192 open https://motorical.com/auth/cli?code=\u2026",
        "Human sets dashboard password via /auth/set-password (establish-password)"
      ]
    },
    {
      "id": "G",
      "name": "Convert prerequisites",
      "actor": "mixed",
      "humanGated": [
        "Stripe checkout for Motorical Plan",
        "publish DNS on customer domain"
      ],
      "actions": [
        "motorical convert --checkout",
        "motorical domain add example.com  OR  MCP motorical_domain_add",
        "Publish verification + DKIM CNAME (cname_managed: use records.dkim as CNAME, not TXT p=)",
        "motorical domain verify <domainId>  OR  MCP motorical_domain_verify (also refreshes sendReady)",
        "If sendReady false after DNS is live: motorical domain check-dns <domainId>  OR  MCP motorical_domain_check_dns"
      ]
    },
    {
      "id": "H",
      "name": "Convert to production domain",
      "actor": "agent",
      "actions": [
        "motorical convert --domain-id <uuid>",
        "POST /api/developer/sandbox/convert { domainId }",
        "Response includes sendReady; if false, run domain check-dns / re-verify",
        "Sandbox DNS reaped; Motor Block re-pointed; outbound lock cleared"
      ]
    },
    {
      "id": "I",
      "name": "Send-ready + production send",
      "actor": "agent",
      "actions": [
        "Confirm sendReady:true via domain verify or check-dns (persists dkim_configured + spf_configured)",
        "POST /v1/send with Authorization: ApiKey mk_live_... from=<addr@domain> optional fromName display name",
        "Do not set a From custom header (DISALLOWED_HEADERS) \u2014 use fromName field instead",
        "If 403 DOMAIN_DNS_INCOMPLETE: check-dns then retry (do not assume dig alone is enough)"
      ],
      "notes": [
        "Ownership verified \u2260 send-ready. /v1/send gates on persisted DKIM/SPF flags.",
        "GET /api/public/v1/motor-blocks/{id}/domain-health uses Public API bearer (ak_live_ mint), not dashboard JWT."
      ]
    }
  ],
  "dkimRule": {
    "cname_managed": "Publish CNAME motorical1._domainkey \u2192 <target>. Do not publish legacy TXT p= from stale agents reading old records.dkim shapes; API now returns CNAME in records.dkim and dnsRecords[]."
  },
  "sendReadiness": {
    "requiredFlags": [
      "dkim_configured",
      "spf_configured"
    ],
    "agentEndpoints": [
      "POST /api/domains/{id}/verify (re-call after ownership)",
      "POST /api/domains/{id}/check-dns"
    ],
    "cli": [
      "motorical domain verify <id>",
      "motorical domain check-dns <id>"
    ],
    "errorCode": "DOMAIN_DNS_INCOMPLETE"
  },
  "related": {
    "humanReport": "https://docs.motorical.com is not the ops report host; ops report lives in motorical-docs runbooks",
    "mcpPage": "https://docs.motorical.com/ai-mcp",
    "aiQuickstart": "https://docs.motorical.com/ai"
  }
}
