{
 "openapi": "3.1.0",
 "info": {
  "title": "Agent Pass API",
  "version": "2026-09-21",
  "description": "Identity, accounts and billing for AI agents and their users. Agents and humans share one subject model. A user holds a time-limited, revocable Agent Pass that products accept. Machine-readable brief: https://agent-pass.turingcorp.net/index.md",
  "contact": {
   "email": "support@agent-pass.turingcorp.net"
  }
 },
 "servers": [
  {
   "url": "https://agent-pass.turingcorp.net"
  }
 ],
 "components": {
  "securitySchemes": {
   "bearerAuth": {
    "type": "http",
    "scheme": "bearer",
    "description": "End-user Agent Pass, returned by sign-up or sign-in."
   },
   "productAuth": {
    "type": "http",
    "scheme": "bearer",
    "description": "Product credential issued to a consumer (Workflow, CloudPal, a channel). One per product, individually revocable."
   },
   "cookieAuth": {
    "type": "apiKey",
    "in": "cookie",
    "name": "ap_session",
    "description": "Browser session for the account page, established with email + password and independent of the Agent Pass; the page stays signed in after the Pass expires."
   }
  },
  "schemas": {
   "Error": {
    "type": "object",
    "properties": {
     "ok": {
      "const": false
     },
     "error": {
      "type": "object",
      "required": [
       "code",
       "message"
      ],
      "properties": {
       "code": {
        "type": "string"
       },
       "message": {
        "type": "string"
       },
       "retryAfterSeconds": {
        "type": "integer"
       },
       "attemptsLeft": {
        "type": "integer"
       }
      }
     }
    }
   },
   "Balance": {
    "type": "object",
    "properties": {
     "available": {
      "type": "integer",
      "description": "minor units"
     },
     "held": {
      "type": "integer"
     },
     "total": {
      "type": "integer"
     },
     "currency": {
      "type": "string"
     }
    }
   },
   "Account": {
    "type": "object",
    "properties": {
     "accountId": {
      "type": "string"
     },
     "email": {
      "type": "string"
     },
     "type": {
      "type": "string",
      "enum": [
       "human",
       "agent"
      ]
     }
    }
   },
   "AuthorizeAllowed": {
    "type": "object",
    "properties": {
     "ok": {
      "type": "boolean"
     },
     "allowed": {
      "const": true
     },
     "accountId": {
      "type": "string",
      "description": "The end user it was booked to. Identical to account.accountId from GET /api/v1/me; absent for metered-only callers."
     },
     "holdId": {
      "type": [
       "string",
       "null"
      ]
     },
     "expiresAt": {
      "type": [
       "string",
       "null"
      ]
     },
     "amount": {
      "type": "integer",
      "description": "Minor units held."
     },
     "currency": {
      "type": "string"
     },
     "metered": {
      "type": "boolean"
     }
    }
   },
   "AuthorizeDenied": {
    "type": "object",
    "properties": {
     "ok": {
      "type": "boolean"
     },
     "allowed": {
      "const": false
     },
     "accountId": {
      "type": "string",
      "description": "Present when the end user was resolved (absent for invalid_credential). Same value as GET /api/v1/me."
     },
     "reason": {
      "type": "string",
      "enum": [
       "invalid_credential",
       "insufficient_balance",
       "account_suspended",
       "rate_limited",
       "amount_above_ceiling",
       "invalid_request",
       "idempotency_conflict",
       "service_unavailable"
      ]
     },
     "message": {
      "type": "string"
     },
     "actionUrl": {
      "type": "string"
     }
    }
   },
   "SettleResult": {
    "type": "object",
    "properties": {
     "ok": {
      "type": "boolean"
     },
     "charged": {
      "type": "integer"
     },
     "released": {
      "type": "integer"
     },
     "usageEventId": {
      "type": "string"
     },
     "idempotentReplay": {
      "type": "boolean"
     }
    }
   }
  }
 },
 "paths": {
  "/api/v1/auth/register/start": {
   "post": {
    "summary": "Send a sign-up verification code",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "email"
        ],
        "properties": {
         "email": {
          "type": "string",
          "format": "email"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Code sent (identical response whether or not the address is registered)"
     },
     "429": {
      "description": "Rate limited"
     }
    }
   }
  },
  "/api/v1/auth/register/complete": {
   "post": {
    "summary": "Complete sign-up",
    "description": "Creates the account, sets the browser session cookie, and returns an Agent Pass (apiKey) for products.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "email",
         "code",
         "password"
        ],
        "properties": {
         "email": {
          "type": "string"
         },
         "code": {
          "type": "string"
         },
         "password": {
          "type": "string",
          "minLength": 8
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Account created; apiKey is returned once",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          },
          "account": {
           "$ref": "#/components/schemas/Account"
          },
          "apiKey": {
           "type": "string",
           "description": "Shown once."
          },
          "expiresAt": {
           "type": [
            "string",
            "null"
           ],
           "description": "Agent Pass expiry (ISO 8601)."
          },
          "loginUrl": {
           "type": "string",
           "description": "POST here with email + password for a fresh Agent Pass."
          },
          "rotateUrl": {
           "type": "string",
           "description": "POST here to replace the Agent Pass while one is still valid."
          }
         }
        }
       }
      }
     },
     "400": {
      "description": "Code or password invalid"
     }
    }
   }
  },
  "/api/v1/auth/login": {
   "post": {
    "summary": "Sign in",
    "description": "Sets the browser session cookie and returns a fresh Agent Pass (apiKey) for products.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "email",
         "password"
        ],
        "properties": {
         "email": {
          "type": "string"
         },
         "password": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Signed in",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          },
          "account": {
           "$ref": "#/components/schemas/Account"
          },
          "apiKey": {
           "type": "string"
          },
          "expiresAt": {
           "type": [
            "string",
            "null"
           ],
           "description": "Agent Pass expiry (ISO 8601)."
          },
          "loginUrl": {
           "type": "string",
           "description": "POST here with email + password for a fresh Agent Pass."
          },
          "rotateUrl": {
           "type": "string",
           "description": "POST here to replace the Agent Pass while one is still valid."
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Incorrect email or password"
     }
    }
   }
  },
  "/api/v1/auth/password/reset/start": {
   "post": {
    "summary": "Send a password reset code",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "email"
        ],
        "properties": {
         "email": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Identical response whether or not the address is registered"
     }
    }
   }
  },
  "/api/v1/auth/password/reset/complete": {
   "post": {
    "summary": "Reset password (revokes all existing credentials)",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "email",
         "code",
         "password"
        ],
        "properties": {
         "email": {
          "type": "string"
         },
         "code": {
          "type": "string"
         },
         "password": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Password reset"
     }
    }
   }
  },
  "/api/v1/me": {
   "get": {
    "summary": "Account info and balance",
    "security": [
     {
      "bearerAuth": []
     },
     {
      "cookieAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "Account and balance",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          },
          "account": {
           "$ref": "#/components/schemas/Account"
          },
          "balance": {
           "$ref": "#/components/schemas/Balance"
          },
          "expiresAt": {
           "type": [
            "string",
            "null"
           ],
           "description": "Agent Pass expiry (ISO 8601)."
          },
          "agentPass": {
           "type": [
            "object",
            "null"
           ],
           "description": "State of the Agent Pass this device presented. Informational: it never authenticates the request.",
           "properties": {
            "expiresAt": {
             "type": [
              "string",
              "null"
             ]
            },
            "active": {
             "type": "boolean"
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Invalid or revoked credential"
     }
    }
   }
  },
  "/api/v1/support": {
   "post": {
    "summary": "File a support ticket (no mailbox needed)",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "email",
         "message"
        ],
        "properties": {
         "email": {
          "type": "string"
         },
         "subject": {
          "type": "string"
         },
         "message": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "ticketId"
     },
     "429": {
      "description": "Rate limited"
     }
    }
   }
  },
  "/api/v1/authorize": {
   "post": {
    "summary": "Authorize one call on behalf of an end user (mode B: the caller states the amount)",
    "description": "Mode B: the caller prices its own product, exactly like a merchant telling an acquirer the order total. Two layers of identity: the caller authenticates with its PRODUCT credential, and names the end user with agentPass. A business refusal is HTTP 200 with allowed:false; only an invalid product credential is 401.",
    "security": [
     {
      "productAuth": []
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "agentPass",
         "amount"
        ],
        "properties": {
         "sku": {
          "type": "string",
          "description": "Used for attribution and self-audit only; it carries no price. Required when amount > 0; omit it for an authentication-only call."
         },
         "qty": {
          "type": "integer",
          "minimum": 1,
          "default": 1
         },
         "amount": {
          "type": "integer",
          "description": "Minor units to hold for this call. Required. The hold is exactly this figure; settlement may be less and releases the difference. A per-SKU ceiling rejects absurd values (a bug guard, not pricing). 0 means authentication only: resolve agentPass and return accountId with holdId null; no SKU is needed and nothing is held."
         },
         "holdAmount": {
          "type": "integer",
          "description": "Legacy alias for amount; accepted during transition."
         },
         "idempotencyKey": {
          "type": "string",
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "Replaying the same key returns the same hold."
         },
         "agentPass": {
          "type": "string",
          "description": "The end-user credential issued by Agent Pass; decides whose balance is used."
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Business decision: allowed true or false (HTTP 200 either way)",
      "content": {
       "application/json": {
        "schema": {
         "oneOf": [
          {
           "$ref": "#/components/schemas/AuthorizeAllowed"
          },
          {
           "$ref": "#/components/schemas/AuthorizeDenied"
          }
         ]
        }
       }
      }
     },
     "401": {
      "description": "Invalid or revoked product credential"
     }
    }
   }
  },
  "/api/v1/usage": {
   "post": {
    "summary": "Settle a hold (report usage)",
    "description": "Idempotent on idempotencyKey: a retry returns the original outcome. Settles up to the authorized amount and releases the difference. A hold can be closed once.",
    "security": [
     {
      "productAuth": []
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "holdId",
         "amount"
        ],
        "properties": {
         "holdId": {
          "type": "string"
         },
         "amount": {
          "type": "integer",
          "description": "Minor units actually billed; must not exceed the authorized amount. 0 means full release (no deliverable)."
         },
         "productStatus": {
          "type": "string",
          "description": "The caller own status code, stored for later audit."
         },
         "idempotencyKey": {
          "type": "string",
          "maxLength": 128
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Settled",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/SettleResult"
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/products/usage": {
   "get": {
    "summary": "Consumer self-audit: its own usage events",
    "security": [
     {
      "productAuth": []
     }
    ],
    "parameters": [
     {
      "name": "from",
      "in": "query",
      "schema": {
       "type": "string",
       "format": "date-time"
      }
     },
     {
      "name": "to",
      "in": "query",
      "schema": {
       "type": "string",
       "format": "date-time"
      }
     },
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 500,
       "default": 100
      }
     }
    ],
    "responses": {
     "200": {
      "description": "{ totals, events[] } - scoped to the calling product only"
     }
    }
   }
  },
  "/api/v1/products/holds": {
   "get": {
    "summary": "Open holds (reconciliation path for parked money)",
    "security": [
     {
      "productAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "{ openHolds[] }"
     }
    }
   }
  },
  "/api/v1/products/skus": {
   "get": {
    "summary": "List this product own SKU declarations",
    "security": [
     {
      "productAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "{ skus[] } with maxAmount and holdTtlSeconds per SKU"
     }
    }
   },
   "post": {
    "summary": "Declare SKU behaviour limits (ceiling + hold window; no price list)",
    "security": [
     {
      "productAuth": []
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "sku"
        ],
        "properties": {
         "sku": {
          "type": "string"
         },
         "maxAmount": {
          "type": "integer",
          "description": "Per-call ceiling in minor units; a bug guard, not a price. Omit for the default."
         },
         "holdTtlSeconds": {
          "type": "integer",
          "minimum": 60,
          "description": "Hold window for this SKU; long-running SKUs need a larger value. Omit for the default."
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "SKU declaration stored",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          },
          "sku": {
           "type": "string"
          }
         }
        }
       }
      }
     }
    }
   }
  },
  "/api/v1/products/rotate": {
   "post": {
    "summary": "Revoke every credential of the calling product",
    "security": [
     {
      "productAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "revoked"
     }
    }
   }
  },
  "/api/v1/me/credentials": {
   "get": {
    "summary": "List active credentials for the account",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "{ maxActive, credentials[] }"
     }
    }
   }
  },
  "/api/v1/me/credentials/revoke": {
   "post": {
    "summary": "Revoke one credential",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "credentialId"
        ],
        "properties": {
         "credentialId": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "revoked"
     }
    }
   }
  },
  "/api/v1/auth/logout": {
   "post": {
    "summary": "Sign out: revoke the browser session (and the Bearer credential, if one was used)",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "revoked"
     }
    }
   }
  },
  "/api/v1/topup/options": {
   "get": {
    "summary": "Available top-up amounts and whether Stripe is configured",
    "responses": {
     "200": {
      "description": "{ configured, amounts[] }"
     }
    }
   }
  },
  "/api/v1/topup": {
   "post": {
    "summary": "Create a Stripe Checkout session",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "amount"
        ],
        "properties": {
         "amount": {
          "type": "integer",
          "description": "Minor units; must be one of the offered amounts."
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Checkout session created",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          },
          "checkoutUrl": {
           "type": "string"
          },
          "sessionId": {
           "type": "string"
          },
          "expiresAt": {
           "type": [
            "string",
            "null"
           ],
           "description": "Checkout expiry (ISO 8601); the link is dead after this."
          }
         }
        }
       }
      }
     },
     "503": {
      "description": "Stripe not configured"
     }
    }
   }
  },
  "/api/v1/topup/session": {
   "get": {
    "summary": "Is this Checkout session mine?",
    "description": "Tells the returning browser whether a Checkout session belongs to the authenticated account. A Checkout link can be paid by anyone, so this never reveals which account was topped up.",
    "security": [
     {
      "bearerAuth": []
     },
     {
      "cookieAuth": []
     }
    ],
    "parameters": [
     {
      "name": "sessionId",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Whether this Checkout session belongs to the authenticated account",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          },
          "known": {
           "type": "boolean"
          },
          "mine": {
           "type": "boolean"
          },
          "status": {
           "type": "string"
          },
          "amount": {
           "type": "integer"
          },
          "currency": {
           "type": "string"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Invalid or revoked credential"
     }
    }
   }
  },
  "/api/v1/ledger": {
   "get": {
    "summary": "Customer ledger (end-user credential)",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "parameters": [
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 500,
       "default": 100
      }
     }
    ],
    "responses": {
     "200": {
      "description": "{ available, currency, entries[] } - every movement is explainable"
     }
    }
   }
  },
  "/health": {
   "get": {
    "summary": "Health check",
    "responses": {
     "200": {
      "description": "ok"
     }
    }
   }
  },
  "/api/v1/me/agent-pass/rotate": {
   "post": {
    "summary": "Re-roll the account Agent Pass (revokes every Agent Pass on all devices; browser sessions are kept)",
    "security": [
     {
      "bearerAuth": []
     }
    ],
    "responses": {
     "200": {
      "description": "New Agent Pass (shown once)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          },
          "apiKey": {
           "type": "string",
           "description": "The Agent Pass; the previous one is already revoked."
          },
          "expiresAt": {
           "type": [
            "string",
            "null"
           ],
           "description": "Agent Pass expiry (ISO 8601)."
          }
         }
        }
       }
      }
     }
    }
   }
  }
 }
}