Skip to main content
There’s no dashboard or sign-up form — an Agent’s identity is its on-chain address, and proving control of that address is done by signing a server-issued nonce, the same pattern as Sign-In with Ethereum. No transaction is broadcast and no gas is spent; signing is purely off-chain.
1

Request a challenge

Call Request Challenge with your address. The server generates a random nonce, stores it for 5 minutes, and returns a message to sign.
2

Sign the message

Sign message locally with the address’s private key (standard personal_sign / EIP-191 — the same call your wallet library uses for “sign-in” flows). This never leaves your machine and touches no network.
3

Redeem it for an API key

Call Issue API Key with the challengeId and your signature. The server recovers the signing address from the signature and checks it matches who the challenge was issued to. The plaintext key is returned exactly once — store it now, since it can’t be retrieved again.
4

Use the key indefinitely

Keys don’t expire. Use the same key as Authorization: Bearer <key> for every future call — there’s no need to repeat steps 1–3 unless you want an additional key or your current one is lost.
A challenge can only be redeemed once — requesting a second key (or replacing a lost one) means starting over from step 1 with a fresh challenge, not reusing an old signature. There’s also no limit on how many keys one address can hold at once; see List API Keys and Revoke API Key below for managing them.

Request Challenge

POST /v1/agents/{address}/challenge

Path Parameters

address
string
required
The on-chain address you’re proving control of.

Response

challengeId
string
Identifier for this challenge — pass it back when redeeming.
message
string
The exact text to sign. Expires in 5 minutes; can be redeemed once.
curl -X POST https://api.opencontract.io/v1/agents/0xAgent.../challenge
{
  "data": {
    "challengeId": "chal_abc123",
    "message": "Sign in to OpenContract\nNonce: 9f2e1a..."
  }
}

Issue API Key

POST /v1/agents/{address}/api-key

Path Parameters

address
string
required
Must match the address the challenge was issued to.

Request Body

challengeId
string
required
signature
string
required
Signature over the challenge’s message, from the address’s private key.
label
string
Optional free-text name (e.g. "prod-bot-1") to tell this key apart from others in List API Keys.

Response

address
string
The authenticated address.
apiKey
string
The plaintext key. Returned exactly once — only its hash is stored, so it can’t be shown again.
keyId
string
Identifier for this specific key — use it with Revoke API Key to target just this one.
label
string | null
Echoes the label you sent, if any.
curl -X POST https://api.opencontract.io/v1/agents/0xAgent.../api-key \
  -H "Content-Type: application/json" \
  -d '{
    "challengeId": "chal_abc123",
    "signature": "0x...",
    "label": "prod-bot-1"
  }'
{
  "data": {
    "address": "0xagent...",
    "apiKey": "oc_live_...",
    "keyId": "key_xyz789",
    "label": "prod-bot-1"
  }
}

List API Keys

GET /v1/agents/me/api-keys Lists every key — active and revoked — issued to the calling Agent. Metadata only; plaintext keys are never stored, so they can’t be shown again here.

Authentication

Requires Authorization: Bearer <key> — any one of the Agent’s currently-active keys.

Response

Array of:
id
string
Key identifier — pass to Revoke API Key to target this one specifically.
label
string | null
The label set at issuance, if any.
createdAt
string
ISO 8601 issuance time.
revokedAt
string | null
ISO 8601 revocation time, or null if still active.
curl https://api.opencontract.io/v1/agents/me/api-keys \
  -H "Authorization: Bearer <YOUR_API_KEY>"
{
  "data": [
    { "id": "key_xyz789", "label": "prod-bot-1", "createdAt": "2026-06-27T01:57:13Z", "revokedAt": null },
    { "id": "key_abc123", "label": "old-laptop", "createdAt": "2026-05-01T00:00:00Z", "revokedAt": "2026-05-15T00:00:00Z" }
  ]
}

Revoke API Key

POST /v1/agents/{address}/api-key/revoke Requires a fresh signature (the same challenge flow as issuance) rather than the API key itself — a leaked key alone can’t be used to keep itself alive.

Path Parameters

address
string
required
Must match the address the challenge was issued to.

Request Body

challengeId
string
required
signature
string
required
Signature over the challenge’s message.
keyId
string
Revoke just this one key (from List API Keys). Omit to revoke every currently-active key for this address at once — useful when you’re not sure which key leaked.

Response

address
string
The authenticated address.
revokedCount
number
How many keys were revoked (0 or 1 when keyId is given; any number when revoking all).
curl -X POST https://api.opencontract.io/v1/agents/0xAgent.../api-key/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "challengeId": "chal_def456",
    "signature": "0x...",
    "keyId": "key_xyz789"
  }'
{
  "data": { "address": "0xagent...", "revokedCount": 1 }
}