Base URL:
https://auth.writesomething.funAdmin UI:https://authadmin.writesomething.funSource: github.com/mandarwagh9/MachineAuth Version: 2.12.61 Protocol: OAuth 2.0 (RFC 6749) with client_credentials & refresh_token grants Token Format: JWT (RS256-signed, RSA 2048-bit keys) Content-Type: All requests and responses useapplication/json(OAuth token endpoint also acceptsapplication/x-www-form-urlencoded) All JSON keys usesnake_case
I am MachineAuth — a self-hosted OAuth 2.0 authentication platform for AI agents and machine-to-machine communication. I issue JWT access tokens, manage agent identities, track audit logs, deliver webhooks, and support multi-tenant organizations with teams.
- An agent is created via the Admin API → receives a
client_idandclient_secret - The agent requests a token by POSTing credentials to
/oauth/token - I return a JWT access token (RS256-signed) + a refresh token
- The agent uses the token as a Bearer token in
Authorizationheaders - Any service can verify the token using my public keys at
/.well-known/jwks.json - Tokens can be introspected via
/oauth/introspectand revoked via/oauth/revoke - Tokens can be refreshed via
/oauth/refreshbefore they expire
Every access token I issue contains these claims:
| Claim | Type | Description |
|---|---|---|
iss |
string | Issuer — https://auth.example.com (configurable via JWT_ISSUER env var) |
sub |
string | Subject — the agent's client_id |
agent_id |
string | The agent's UUID |
org_id |
string | Organization ID the agent belongs to |
team_id |
string | Team ID (if assigned) |
aud |
string | Audience — machineauth-api |
iat |
number | Issued at (Unix timestamp) |
exp |
number | Expiration (Unix timestamp) |
scope |
array | Array of scope strings granted to this token |
jti |
string | Unique token ID (used for revocation tracking) |
| Token | Default Lifetime | Notes |
|---|---|---|
| Access Token | 3600s (1 hour) in production, 86400s (24 hours) in development | Configurable via JWT_ACCESS_TOKEN_EXPIRY env var |
| Refresh Token | 604800s (7 days) | Fixed |
Description: Service info and available endpoints. Auth: None
Response 200:
{
"service": "MachineAuth",
"status": "running",
"version": "2.12.61",
"endpoints": "/oauth/token, /oauth/introspect, /oauth/revoke, /oauth/refresh, /api/agents, /.well-known/jwks.json"
}Description: Basic health check. Auth: None
Response 200:
{
"status": "ok"
}Description: Readiness check with agent count. Auth: None
Response 200:
{
"status": "ok",
"timestamp": "2026-03-02T12:00:00Z",
"agents_count": 5
}Description: Service metrics (token counts, agent counts). Auth: None
Response 200:
{
"requests": 0,
"tokens_issued": 42,
"tokens_refreshed": 10,
"tokens_revoked": 3,
"active_tokens": 39,
"total_agents": 5
}Description: JSON Web Key Set — public keys for verifying JWT signatures. Auth: None
Response 200:
{
"keys": [
{
"kty": "RSA",
"kid": "key-1",
"use": "sig",
"alg": "RS256",
"n": "<base64url-encoded RSA modulus>",
"e": "<base64url-encoded RSA exponent>"
}
]
}How to use: Fetch this endpoint, find the key matching the kid header in the JWT, and verify the RS256 signature using the public key constructed from n and e.
Description: Obtain an access token using client credentials or a refresh token.
Auth: None (credentials are in the body)
Content-Type: application/json or application/x-www-form-urlencoded
Request body (client_credentials grant):
{
"grant_type": "client_credentials",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_secret": "your-secret-here",
"scope": "read write"
}| Field | Type | Required | Description |
|---|---|---|---|
grant_type |
string | Yes | Must be client_credentials or refresh_token |
client_id |
string | Yes (for client_credentials) | The agent's client ID (UUID) |
client_secret |
string | Yes (for client_credentials) | The agent's client secret |
scope |
string | No | Space or comma-separated scopes to request (filtered against agent's allowed scopes) |
refresh_token |
string | Yes (for refresh_token) | The refresh token string |
Response 200:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write",
"issued_at": 1709380800,
"refresh_token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Error 400:
{
"error": "invalid_client",
"error_description": "invalid client credentials"
}Possible error codes:
| Error | Description |
|---|---|
invalid_request |
Missing or malformed parameters |
invalid_client |
Bad client_id or client_secret |
invalid_grant |
Invalid or expired refresh token |
unsupported_grant_type |
grant_type must be client_credentials or refresh_token |
server_error |
Internal error generating token |
Description: Check if an access token is valid and get its metadata.
Auth: None
Content-Type: application/json or application/x-www-form-urlencoded
Request body:
{
"token": "eyJhbGciOiJSUzI1NiIs..."
}Response 200 (active token):
{
"active": true,
"scope": "read write",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"exp": 1709384400,
"iat": 1709380800,
"token_type": "Bearer"
}Response 200 (inactive token):
{
"active": false,
"revoked": true,
"reason": "revoked"
}reason values |
Description |
|---|---|
revoked |
Token was explicitly revoked |
expired |
Token has expired |
| (empty) | Token is malformed or unrecognized |
Description: Revoke an access token or refresh token.
Auth: None
Content-Type: application/json or application/x-www-form-urlencoded
Request body:
{
"token": "eyJhbGciOiJSUzI1NiIs...",
"token_type_hint": "access_token"
}| Field | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | The token to revoke |
token_type_hint |
string | No | access_token or refresh_token. If omitted, both types are tried. |
Response 200:
{
"status": "revoked"
}Description: Refresh an access token using a refresh token.
Auth: None
Content-Type: application/json or application/x-www-form-urlencoded
Request body:
{
"grant_type": "refresh_token",
"refresh_token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_id": "optional-client-id",
"client_secret": "optional-client-secret"
}| Field | Type | Required | Description |
|---|---|---|---|
refresh_token |
string | Yes | The refresh token |
client_id |
string | No | If provided with client_secret, validates client credentials too |
client_secret |
string | No | Used with client_id for additional validation |
Response 200: Same as /oauth/token response (new access token).
These endpoints manage agent identities. Currently protected by admin session (Basic Auth with ADMIN_EMAIL / ADMIN_PASSWORD).
Description: List all agents. Auth: Admin
Response 200:
{
"agents": [
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"organization_id": "org-123",
"team_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"name": "OpenClaw Agent",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scopes": ["read", "write", "admin"],
"is_active": true,
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:00Z",
"expires_at": "2026-06-01T10:00:00Z",
"token_count": 15,
"refresh_count": 3,
"last_activity_at": "2026-03-02T09:00:00Z",
"last_token_issued_at": "2026-03-02T09:00:00Z"
}
]
}Description: Create a new agent. Returns client credentials (secret shown only once). Auth: Admin
Request body:
{
"name": "OpenClaw Agent",
"organization_id": "org-123",
"team_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scopes": ["read", "write"],
"expires_in": 7776000
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable agent name |
organization_id |
string | No | Organization to assign agent to |
team_id |
string (UUID) | No | Team to assign agent to |
scopes |
array of strings | No | Scopes the agent is allowed (defaults to []) |
expires_in |
integer | No | Seconds until the agent credential expires. Omit for no expiry. |
Response 201:
{
"agent": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"name": "OpenClaw Agent",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scopes": ["read", "write"],
"is_active": true,
"created_at": "2026-03-02T12:00:00Z",
"updated_at": "2026-03-02T12:00:00Z",
"expires_at": "2026-06-01T12:00:00Z",
"token_count": 0,
"refresh_count": 0
},
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_secret": "abcdef1234567890abcdef1234567890abcdef123456"
}IMPORTANT: The
client_secretis only returned at creation time. Store it securely. It cannot be retrieved later — only rotated.
Description: Get a single agent by UUID. Auth: Admin
Response 200:
{
"agent": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"organization_id": "org-123",
"name": "OpenClaw Agent",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scopes": ["read", "write"],
"is_active": true,
"created_at": "2026-03-02T12:00:00Z",
"updated_at": "2026-03-02T12:00:00Z",
"token_count": 15,
"refresh_count": 3,
"last_activity_at": "2026-03-02T09:00:00Z",
"last_token_issued_at": "2026-03-02T09:00:00Z"
}
}Error 404: Agent not found.
Description: Delete an agent permanently. Auth: Admin
Response 204: No content (success).
Description: Rotate an agent's client secret. The old secret is immediately invalidated. Auth: Admin
Request body:
{
"action": "rotate"
}Response 200:
{
"client_secret": "new-secret-value-shown-only-once"
}IMPORTANT: The new secret is only shown once. All existing tokens remain valid until expiry, but the old secret can no longer be used to get new tokens.
These endpoints let an authenticated agent manage itself. Requires Authorization: Bearer <access_token> header.
Description: Get the authenticated agent's own profile. Auth: Bearer token
Response 200:
{
"agent": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"name": "OpenClaw Agent",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scopes": ["read", "write"],
"is_active": true,
"created_at": "2026-03-02T12:00:00Z",
"updated_at": "2026-03-02T12:00:00Z",
"token_count": 15,
"refresh_count": 3
}
}Description: Get the authenticated agent's usage statistics and rotation history. Auth: Bearer token
Response 200:
{
"agent": { "...agent object..." },
"organization_id": "org-123",
"team_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"token_count": 15,
"refresh_count": 3,
"last_activity_at": "2026-03-02T09:00:00Z",
"last_token_issued_at": "2026-03-02T09:00:00Z",
"rotation_history": [
{
"rotated_at": "2026-02-15T08:00:00Z",
"rotated_by_ip": "192.168.1.1"
}
]
}Description: Agent rotates its own credentials. Old secret is invalidated immediately. Auth: Bearer token
Response 200:
{
"client_secret": "new-secret-shown-only-once"
}Description: Agent deactivates itself. It will no longer be able to get new tokens. Auth: Bearer token
Response 200:
{
"message": "agent deactivated successfully"
}Description: Agent reactivates itself. Auth: Bearer token
Response 200:
{
"message": "agent reactivated successfully"
}Description: Agent permanently deletes itself. Auth: Bearer token
Response 204: No content (success).
Description: Verify the Bearer token and return the authenticated agent's identity. Auth: Bearer token
Response 200:
{
"valid": true,
"agent_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"name": "OpenClaw Agent",
"scopes": ["read", "write"],
"is_active": true,
"token_count": 15
}Error 401: Invalid or expired token.
Description: List all organizations. Auth: Admin
Response 200:
{
"organizations": [
{
"id": "org-123",
"name": "My Organization",
"slug": "my-org",
"owner_email": "owner@example.com",
"jwt_issuer": "",
"jwt_expiry_secs": 0,
"allowed_origins": "",
"plan": "free",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:00Z"
}
]
}Description: Create a new organization. Auth: Admin
Request body:
{
"name": "My Organization",
"slug": "my-org",
"owner_email": "owner@example.com"
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Organization display name |
slug |
string | Yes | URL-friendly identifier |
owner_email |
string | No | Owner's email address |
Response 201: The created organization object.
Description: Get a single organization. Auth: Admin
Response 200: Organization object.
Description: Update an organization. Auth: Admin
Request body:
{
"name": "Updated Name",
"jwt_issuer": "https://custom-issuer.example.com",
"jwt_expiry_secs": 7200,
"allowed_origins": "https://myapp.com,https://other.com"
}Description: Delete an organization. Auth: Admin
Response 204: No content.
Description: List all teams in an organization. Auth: Admin
Response 200:
{
"teams": [
{
"id": "team-uuid",
"organization_id": "org-123",
"name": "Backend Team",
"description": "Handles backend services",
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:00Z"
}
]
}Description: Create a team within an organization. Auth: Admin
Request body:
{
"name": "Backend Team",
"description": "Handles backend services"
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Team name |
description |
string | No | Team description |
Response 201: The created team object.
Description: List all agents belonging to an organization. Auth: Admin
Response 200:
{
"agents": [ "...array of agent objects..." ]
}Description: Create an agent within a specific organization. Auth: Admin
Request body: Same as POST /api/agents (the organization_id is set from the URL).
Response 201: Same as POST /api/agents.
Description: List all API keys for an organization. Auth: Admin
Response 200:
{
"api_keys": [
{
"id": "key-uuid",
"organization_id": "org-123",
"team_id": "team-uuid",
"name": "Production Key",
"prefix": "mach_abc123de",
"last_used_at": "2026-03-02T09:00:00Z",
"expires_at": "2026-06-01T10:00:00Z",
"is_active": true,
"created_at": "2026-03-01T10:00:00Z"
}
]
}Description: Create an API key for an organization. Auth: Admin
Request body:
{
"name": "Production Key",
"team_id": "optional-team-uuid",
"expires_in": 7776000
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Key display name |
team_id |
string (UUID) | No | Team to scope the key to |
expires_in |
integer | No | Seconds until expiry |
Response 201:
{
"api_key": {
"id": "key-uuid",
"organization_id": "org-123",
"name": "Production Key",
"prefix": "mach_abc123de",
"is_active": true,
"created_at": "2026-03-02T12:00:00Z"
},
"key": "mach_abc123defull-api-key-shown-only-once"
}IMPORTANT: The full
keyvalue is shown only at creation time.
Description: Revoke (delete) an API key. Auth: Admin
Response 204: No content.
Description: List all webhook configurations. Auth: Admin
Response 200:
{
"webhooks": [
{
"id": "webhook-uuid",
"organization_id": "",
"team_id": "",
"name": "My Webhook",
"url": "https://example.com/webhook",
"events": ["agent.created", "agent.deleted"],
"is_active": true,
"max_retries": 10,
"retry_backoff_base": 2,
"created_at": "2026-03-01T10:00:00Z",
"updated_at": "2026-03-01T10:00:00Z",
"last_tested_at": null,
"consecutive_fails": 0
}
]
}Description: Create a webhook. A signing secret is generated automatically. Auth: Admin
Request body:
{
"name": "My Webhook",
"url": "https://example.com/webhook",
"events": ["agent.created", "agent.deleted", "token.issued"],
"max_retries": 10,
"retry_backoff_base": 2
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Webhook display name |
url |
string | Yes | The HTTPS URL to deliver events to |
events |
array of strings | Yes | Which events to subscribe to (see events list below) |
max_retries |
integer | No | Max delivery retries (default: 10) |
retry_backoff_base |
integer | No | Exponential backoff base in seconds (default: 2) |
Response 201:
{
"webhook": { "...webhook object..." },
"secret": "webhook-signing-secret-shown-only-once"
}IMPORTANT: The
secretis shown only once. Use it to verify webhook signatures via HMAC-SHA256.
Description: Get a single webhook configuration. Auth: Admin
Response 200:
{
"webhook": { "...webhook object..." }
}Description: Update a webhook. Auth: Admin
Request body (all fields optional):
{
"name": "Updated Name",
"url": "https://new-url.example.com/webhook",
"events": ["agent.created"],
"is_active": false,
"max_retries": 5,
"retry_backoff_base": 3
}Description: Delete a webhook. Auth: Admin
Response 204: No content.
Description: Send a test event to a webhook URL. Auth: Admin
Request body:
{
"event": "webhook.test",
"payload": "{\"test\": true}"
}Response 200:
{
"success": true,
"status_code": 200,
"error": ""
}Description: List delivery history for a webhook. Auth: Admin
Response 200:
{
"deliveries": [
{
"id": "delivery-uuid",
"webhook_config_id": "webhook-uuid",
"event": "agent.created",
"payload": "{...}",
"headers": "",
"status": "delivered",
"attempts": 1,
"last_attempt_at": "2026-03-02T12:00:01Z",
"last_error": "",
"next_retry_at": null,
"created_at": "2026-03-02T12:00:00Z"
}
]
}Delivery status values:
| Status | Description |
|---|---|
pending |
Not yet attempted |
delivered |
Successfully delivered (HTTP 2xx) |
failed |
Delivery failed, no more retries |
retrying |
Failed, will retry with exponential backoff |
dead |
Max retries exceeded |
Description: Get a single delivery record. Auth: Admin
Response 200:
{
"delivery": { "...delivery object..." }
}Description: List all available webhook event types. Auth: Admin
Response 200:
{
"events": [
"agent.created",
"agent.deleted",
"agent.updated",
"agent.credentials_rotated",
"token.issued",
"token.validation_success",
"token.validation_failed",
"webhook.created",
"webhook.updated",
"webhook.deleted",
"webhook.test"
]
}| Event | Fired When | Payload Fields |
|---|---|---|
agent.created |
A new agent is created | event, agent_id, agent_name, client_id, timestamp |
agent.deleted |
An agent is deleted | event, agent_id, timestamp |
agent.updated |
An agent is modified | event, agent_id, timestamp |
agent.credentials_rotated |
An agent's secret is rotated | event, agent_id, timestamp |
token.issued |
An access token is issued | event, agent_id, timestamp |
token.validation_success |
A token passes introspection | event, agent_id, success, timestamp |
token.validation_failed |
A token fails introspection | event, agent_id, success, timestamp |
webhook.created |
A webhook config is created | event, webhook_id, timestamp |
webhook.updated |
A webhook config is updated | event, webhook_id, timestamp |
webhook.deleted |
A webhook config is deleted | event, webhook_id, timestamp |
webhook.test |
A test delivery is triggered | event, webhook_id, timestamp |
Every webhook delivery includes an HMAC-SHA256 signature in the X-Webhook-Signature header. To verify:
expected = HMAC-SHA256(webhook_secret, request_body)
actual = request.headers["X-Webhook-Signature"]
if expected == actual → delivery is authentic
{
"id": "UUID",
"organization_id": "string",
"team_id": "UUID | null",
"name": "string",
"client_id": "UUID (auto-generated)",
"scopes": ["string"],
"public_key": "string | null (for DPoP/mTLS future use)",
"is_active": "boolean",
"created_at": "ISO 8601 timestamp",
"updated_at": "ISO 8601 timestamp",
"expires_at": "ISO 8601 timestamp | null",
"token_count": "integer",
"refresh_count": "integer",
"last_activity_at": "ISO 8601 timestamp | null",
"last_token_issued_at": "ISO 8601 timestamp | null"
}{
"id": "string",
"name": "string",
"slug": "string",
"owner_email": "string",
"jwt_issuer": "string",
"jwt_expiry_secs": "integer",
"allowed_origins": "string (comma-separated)",
"plan": "string (free|pro|enterprise)",
"created_at": "ISO 8601 timestamp",
"updated_at": "ISO 8601 timestamp"
}{
"id": "string",
"organization_id": "string",
"name": "string",
"description": "string",
"created_at": "ISO 8601 timestamp",
"updated_at": "ISO 8601 timestamp"
}{
"id": "string",
"organization_id": "string",
"team_id": "UUID | null",
"name": "string",
"prefix": "string (first 12 chars of key)",
"last_used_at": "ISO 8601 timestamp | null",
"expires_at": "ISO 8601 timestamp | null",
"is_active": "boolean",
"created_at": "ISO 8601 timestamp"
}{
"id": "UUID",
"organization_id": "string",
"team_id": "string",
"name": "string",
"url": "string (HTTPS URL)",
"events": ["string"],
"is_active": "boolean",
"max_retries": "integer (default: 10)",
"retry_backoff_base": "integer (default: 2)",
"created_at": "ISO 8601 timestamp",
"updated_at": "ISO 8601 timestamp",
"last_tested_at": "ISO 8601 timestamp | null",
"consecutive_fails": "integer"
}{
"id": "UUID",
"webhook_config_id": "UUID",
"event": "string",
"payload": "string (JSON)",
"headers": "string",
"status": "pending | delivered | failed | retrying | dead",
"attempts": "integer",
"last_attempt_at": "ISO 8601 timestamp | null",
"last_error": "string",
"next_retry_at": "ISO 8601 timestamp | null",
"created_at": "ISO 8601 timestamp"
}{
"id": "UUID",
"agent_id": "UUID | null",
"action": "string (event type)",
"ip_address": "string",
"user_agent": "string",
"created_at": "ISO 8601 timestamp"
}{
"error": "string (error code)",
"error_description": "string (human-readable message)"
}| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | invalid_request |
Missing or malformed request parameters |
| 400 | unsupported_grant_type |
grant_type not client_credentials or refresh_token |
| 401 | invalid_client |
Bad client_id or client_secret |
| 401 | invalid_grant |
Invalid or expired refresh token |
| 401 | (plain text) | Missing/invalid Authorization header for Bearer-auth endpoints |
| 404 | (plain text) | Resource not found |
| 405 | (plain text) | HTTP method not allowed for this endpoint |
| 500 | server_error |
Internal server error |
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
Server listen port |
ENV |
development |
Environment (development or production) |
DATABASE_URL |
json:machineauth.json |
PostgreSQL URL or json:filename.json for JSON file storage |
JWT_SIGNING_ALGORITHM |
RS256 |
JWT signing algorithm |
JWT_KEY_ID |
key-1 |
Key ID in JWKS |
JWT_ACCESS_TOKEN_EXPIRY |
3600 |
Access token lifetime in seconds (overridden to 86400 in development) |
ALLOWED_ORIGINS |
http://localhost:3000 |
CORS allowed origins (comma-separated) |
REQUIRE_HTTPS |
false |
Require HTTPS connections |
ADMIN_EMAIL |
admin@example.com |
Admin login email |
ADMIN_PASSWORD |
changeme |
Admin login password |
WEBHOOK_WORKER_COUNT |
3 |
Number of concurrent webhook delivery workers |
WEBHOOK_MAX_RETRIES |
10 |
Max webhook delivery retry attempts |
WEBHOOK_TIMEOUT_SECS |
10 |
Webhook HTTP request timeout |
Your admin creates an agent for you and gives you a client_id and client_secret.
curl -X POST https://auth.writesomething.fun/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'curl https://auth.writesomething.fun/api/agents/me \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"curl -X POST https://auth.writesomething.fun/oauth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "YOUR_REFRESH_TOKEN"
}'curl https://auth.writesomething.fun/api/verify \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"┌──────────────────────────────────────────────────────┐
│ MachineAuth │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────┐ ┌────────┐ │
│ │ OAuth │ │ Agent │ │ Audit │ │Webhook │ │
│ │ Engine │ │ Manager │ │ Log │ │ System │ │
│ │ │ │ │ │ │ │ │ │
│ │ /oauth/* │ │/api/agent│ │ Every │ │ HMAC │ │
│ │ RS256 JWT│ │ CRUD + │ │ action│ │ signed │ │
│ │ JWKS │ │ self-svc │ │logged │ │delivery│ │
│ └────┬─────┘ └────┬─────┘ └───┬───┘ └───┬────┘ │
│ │ │ │ │ │
│ ┌────┴──────────────┴────────────┴──────────┴─────┐ │
│ │ Core Services Layer │ │
│ │ Organizations · Teams · API Keys · Tokens │ │
│ └─────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌─────────────────────┴───────────────────────────┐ │
│ │ Storage Layer │ │
│ │ PostgreSQL 15 (production) │ │
│ │ JSON File (development) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Middleware │ │
│ │ CORS · Request Logging · JWT Auth │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | / |
None | Service info |
| GET | /health |
None | Health check |
| GET | /health/ready |
None | Readiness + agent count |
| GET | /metrics |
None | Token & agent metrics |
| GET | /.well-known/jwks.json |
None | RSA public keys (JWKS) |
| POST | /oauth/token |
None | Get access + refresh token |
| POST | /oauth/introspect |
None | Validate a token |
| POST | /oauth/revoke |
None | Revoke a token |
| POST | /oauth/refresh |
None | Refresh an access token |
| GET | /api/agents |
Admin | List all agents |
| POST | /api/agents |
Admin | Create agent |
| GET | /api/agents/{id} |
Admin | Get agent |
| DELETE | /api/agents/{id} |
Admin | Delete agent |
| POST | /api/agents/{id} |
Admin | Rotate agent secret |
| GET | /api/agents/me |
Bearer | Get own profile |
| GET | /api/agents/me/usage |
Bearer | Get own usage stats |
| POST | /api/agents/me/rotate |
Bearer | Rotate own secret |
| POST | /api/agents/me/deactivate |
Bearer | Deactivate self |
| POST | /api/agents/me/reactivate |
Bearer | Reactivate self |
| POST/DELETE | /api/agents/me/delete |
Bearer | Delete self |
| GET | /api/verify |
Bearer | Verify token + get identity |
| GET | /api/organizations |
Admin | List organizations |
| POST | /api/organizations |
Admin | Create organization |
| GET | /api/organizations/{id} |
Admin | Get organization |
| PUT/PATCH | /api/organizations/{id} |
Admin | Update organization |
| DELETE | /api/organizations/{id} |
Admin | Delete organization |
| GET | /api/organizations/{id}/teams |
Admin | List teams |
| POST | /api/organizations/{id}/teams |
Admin | Create team |
| GET | /api/organizations/{id}/agents |
Admin | List org agents |
| POST | /api/organizations/{id}/agents |
Admin | Create agent in org |
| GET | /api/organizations/{id}/api-keys |
Admin | List API keys |
| POST | /api/organizations/{id}/api-keys |
Admin | Create API key |
| DELETE | /api/organizations/{id}/api-keys/{key_id} |
Admin | Revoke API key |
| GET | /api/webhooks |
Admin | List webhooks |
| POST | /api/webhooks |
Admin | Create webhook |
| GET | /api/webhooks/{id} |
Admin | Get webhook |
| PUT | /api/webhooks/{id} |
Admin | Update webhook |
| DELETE | /api/webhooks/{id} |
Admin | Delete webhook |
| POST | /api/webhooks/{id}/test |
Admin | Test webhook |
| GET | /api/webhooks/{id}/deliveries |
Admin | Delivery history |
| GET | /api/webhooks/{id}/deliveries/{did} |
Admin | Get delivery |
| GET | /api/webhook-events |
Admin | List event types |
Built with Go 1.23. Secured with RS256. Owned by you. github.com/mandarwagh9/MachineAuth