# Orthogonal agent signup

Create an agent account by verifying both your email address and phone number. A successful signup returns an Orthogonal API key; no password or existing API key is required.

Eligible new accounts receive **$1 in starter credits before the API key is returned**. The offer is one-time per account and phone number, shared with the website's free-credit offer. Repeating registration does not add another $1. A phone that already used the offer cannot receive another grant through a new account.

## Requirements

- A unique email address whose inbox you can access
- First and last name
- A phone number in E.164 format, such as `+14165550123`
- `curl` and `jq`

## 1. Request email and phone verification codes

```sh
API_BASE="https://api.orthogonal.com"
EMAIL="agent@example.com"
FIRST_NAME="Research"
LAST_NAME="Agent"
PHONE_NUMBER="+14165550123"

CHALLENGE=$(curl --fail-with-body --silent --show-error \
  --request POST "$API_BASE/api/agent-signup/send-code" \
  --header "Content-Type: application/json" \
  --data "$(jq -n \
    --arg email "$EMAIL" \
    --arg firstName "$FIRST_NAME" \
    --arg lastName "$LAST_NAME" \
    --arg phoneNumber "$PHONE_NUMBER" \
    '{email: $email, firstName: $firstName, lastName: $lastName, phoneNumber: $phoneNumber, acceptedTerms: true}')")

SIGNUP_ID=$(printf '%s' "$CHALLENGE" | jq -r '.signupId')
VERIFICATION_TOKEN=$(printf '%s' "$CHALLENGE" | jq -r '.verificationToken')
```

Orthogonal sends separate six-digit codes to the email address and phone number. Both expire after five minutes. You must own both identifiers. Wait at least 60 seconds before requesting replacement codes.

## 2. Verify both codes

```sh
read -r -s -p "Phone verification code: " CODE
read -r -s -p "Email verification code: " EMAIL_CODE

VERIFICATION=$(curl --fail-with-body --silent --show-error \
  --request POST "$API_BASE/api/agent-signup/verify-code" \
  --header "Content-Type: application/json" \
  --data "$(jq -n \
    --arg phoneNumber "$PHONE_NUMBER" \
    --arg code "$CODE" \
    --arg emailCode "$EMAIL_CODE" \
    --arg signupId "$SIGNUP_ID" \
    --arg verificationToken "$VERIFICATION_TOKEN" \
    '{phoneNumber: $phoneNumber, code: $code, emailCode: $emailCode, signupId: $signupId, verificationToken: $verificationToken}')")

SIGNUP_PROOF=$(printf '%s' "$VERIFICATION" | jq -r '.signupProof')
```

## 3. Create the account and receive its API key

```sh
ACCOUNT=$(curl --fail-with-body --silent --show-error \
  --request POST "$API_BASE/api/agent-signup/register" \
  --header "Content-Type: application/json" \
  --data "$(jq -n \
    --arg email "$EMAIL" \
    --arg firstName "$FIRST_NAME" \
    --arg lastName "$LAST_NAME" \
    --arg phoneNumber "$PHONE_NUMBER" \
    --arg signupProof "$SIGNUP_PROOF" \
    '{email: $email, firstName: $firstName, lastName: $lastName, phoneNumber: $phoneNumber, signupProof: $signupProof, acceptedTerms: true}')")

ORTHOGONAL_API_KEY=$(printf '%s' "$ACCOUNT" | jq -r '.apiKey')
export ORTHOGONAL_API_KEY
printf '%s\n' "$ACCOUNT" | jq '{accountId, starterCredit}'
```

Example response:

```json
{
  "accountId": "user_...",
  "apiKey": "orth_agent_...",
  "starterCredit": { "amountUsd": 1, "alreadyGranted": false }
}
```

Save the API key securely. Only its hash is stored in the API-key table; the dashboard cannot reveal it. Use it as `Authorization: Bearer <apiKey>` with the normal Orthogonal API.

If the account or phone is ineligible for the starter offer, registration still returns 201 with its API key, but `starterCredit` is `{ "amountUsd": 0, "alreadyGranted": false, "reason": "phone_unavailable" }` or uses `"reason": "ineligible"`. No starter credits were added. Save the key and follow step 4 to add credits with the account owner's approval; do not create another account to bypass the offer limit.

If the registration response is lost, retry `/register` with the same details and proof. The endpoint returns the same account and key, without creating duplicates. The proof expires after ten minutes. If it expires, request and verify new codes using the same email and phone number. Recovery is available for 24 hours after account creation and never restores a revoked or deleted key. After that, sign in to manage or replace your keys.

Do not change email or names between these steps. Verification is bound to the original details. A `429` response means a shared rate limit was reached; do not retry in a tight loop. A `503` during registration can mean creation is pending—retry the same request. A `409` means signup recovery is unavailable; use sign-in instead.

The examples use Bash and keep secrets in shell variables. Do not run with shell tracing (`set -x`), put codes or keys in URLs, or save responses in shared logs.

For the browser flow, visit [orthogonal.com/agent/signup](https://orthogonal.com/agent/signup).

## 4. Check balance and add credits

```sh
curl --fail-with-body --silent --show-error "$API_BASE/v1/credits/balance" \
  --header "Authorization: Bearer $ORTHOGONAL_API_KEY"
curl --fail-with-body --silent --show-error "$API_BASE/v1/credits/pricing"
```

Read the current packages and total prices. With the account owner's approval, request a Stripe link by email:

```sh
curl --fail-with-body --silent --show-error \
  --request POST "$API_BASE/v1/credits/email-payment-link" \
  --header "Authorization: Bearer $ORTHOGONAL_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"credits":25}'
```

This package adds $25 in credits; the current total is $26.25 including the 5% processing fee. Always use `/v1/credits/pricing` for current prices. The link goes to the account's verified primary email; custom recipients, account IDs and redirects are not accepted. Personal API keys only; organization purchases use the dashboard.

The response includes `emailSent`, `email`, `credits`, `totalLabel` and `checkoutUrl`. If email delivery fails (`emailSent: false`), use that checkout URL instead of creating more links. Limits are one request per minute and five per account per day. Honor 429 responses and do not retry in a loop.

Requesting a link does not charge anything or add credits. The owner pays through Stripe, and the existing payment webhook loads the credits after successful payment. Check balance again after payment confirmation. Do not complete payment without explicit authorization.

If the starter-credit service is temporarily unavailable, registration returns 503 rather than an unfunded key. Retry the same registration; account creation and the $1 grant are idempotent. `alreadyGranted: true` means the starter grant was previously applied, not that the current balance is still $1. Keep checkout URLs private.
