# API quickstart

> Go from zero to your first successful API call in ten minutes — register a client, create a sandbox, run the OAuth flow, and create an invoice against demo data.

Source: https://www.accountfun.com/docs/api/quickstart/

This walkthrough takes you from a brand-new client application to a successful API call. You’ll register a client, create a sandbox organization, run the OAuth authorization flow against it, exchange the code for tokens, and create an invoice. You can do it entirely from a terminal with `curl`, plus `openssl` or Node.js to generate the PKCE values.

The whole walkthrough runs against your **sandbox** — a non-live organization seeded with demo data — so nothing you do here touches real books. All you need is an AccountFUN account with a confirmed email address. See [Sandbox](https://www.accountfun.com/docs/api/sandbox/) for the full sandbox lifecycle, lockdown guarantees, and limits.

## 1. Register a client application

1. Sign in to AccountFUN, go to **Settings → API & Developers** and click **New App**.
2. Set the **name** to something like “My Integration”.
3. Add a **redirect URI**. For local testing, use `http://localhost:3000/callback`.
4. Select the scopes: `contacts:read`, `invoices:read`, `invoices:write`, `invoices:approve`, `chart_of_accounts:read`, `sandbox:reset`, and `offline_access`.
5. Save. Copy the **client ID** and **client secret** now — the secret is only shown once.

Export them as environment variables for the rest of this guide:

```bash
export CLIENT_ID="your_client_id_here"
export CLIENT_SECRET="your_client_secret_here"
export REDIRECT_URI="http://localhost:3000/callback"
```

## 2. Create a sandbox

Go back to **Settings → API & Developers**. The sandbox card sits at the top of the page — click **Create sandbox**.

AccountFUN creates a second, non-live organization seeded with demo data: contacts at `@example.com` addresses, and invoices and bills spanning the document lifecycle. It appears in your organization switcher badged **(Sandbox)**, and nothing you do in it can reach a real inbox or move real money. One sandbox per developer; you can reset it to its fresh seed at any time.

## 3. Send the user to authorize

Every authorize request needs a random `state` and a [PKCE](https://www.accountfun.com/docs/api/authentication/#pkce-required) pair: a secret `code_verifier` (43–128 characters) that you keep for step 4, and its `code_challenge` — the base64url-encoded SHA-256 of the verifier, without `=` padding — which goes in the URL. Generate all three in the same terminal:

```bash
export STATE=$(openssl rand -hex 16)
export CODE_VERIFIER=$(openssl rand -base64 48 | tr -d '\n=' | tr '+/' '-_')
export CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr -d '\n=' | tr '+/' '-_')
```

No `openssl`? Node.js 16 or newer does the same:

```bash
eval "$(node -e '
const c = require("node:crypto");
const v = c.randomBytes(32).toString("base64url");
const ch = c.createHash("sha256").update(v).digest("base64url");
console.log(`export STATE=${c.randomBytes(16).toString("hex")} CODE_VERIFIER=${v} CODE_CHALLENGE=${ch}`);
')"
```

Then print the authorize URL:

```bash
echo "https://app.accountfun.com/api/v1/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&scope=contacts:read%20invoices:read%20invoices:write%20invoices:approve%20chart_of_accounts:read%20sandbox:reset%20offline_access&state=$STATE&code_challenge=$CODE_CHALLENGE&code_challenge_method=S256"
```

Open the printed URL in a browser where you’re signed in to AccountFUN. You’ll see the consent screen. The organization picker lists your sandbox with a **(Sandbox)** suffix — pick it deliberately, because the token is bound to whichever organization you choose. Review the scopes. Approving invoices is listed under **Only if you tick it:** — tick it, because step 7 approves an invoice. Then click **Allow**.

AccountFUN redirects back to your redirect URI with a `code`. If nothing is running on `localhost:3000`, the browser shows an error page; either way, the code is in its address bar:

```text
http://localhost:3000/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE
```

Verify state

The `state` in the address bar must match `$STATE` (`echo $STATE`). A real app refuses the callback when it doesn’t. For this walkthrough, just copy the `code` parameter.

Copy the code:

```bash
export CODE="the_authorization_code"
```

The code works once and expires after 60 seconds, so run step 4 straight away. If it has expired, repeat step 3.

## 4. Exchange the code for tokens

Send the `code_verifier` from step 3 with the code. AccountFUN checks that its SHA-256 matches the `code_challenge` you sent, and that `redirect_uri` is exactly the one used in step 3:

```bash
curl -X POST https://app.accountfun.com/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri="$REDIRECT_URI" \
  -d client_id="$CLIENT_ID" \
  -d client_secret="$CLIENT_SECRET" \
  -d code_verifier="$CODE_VERIFIER"
```

A missing or wrong `code_verifier` returns `400 invalid_grant`; the code stays usable until it expires, so you can retry with the right verifier.

You’ll receive:

```json
{
  "access_token": "ab12cd34...",
  "token_type": "Bearer",
  "expires_in": 1800,
  "scope": "contacts:read invoices:read invoices:write invoices:approve chart_of_accounts:read sandbox:reset offline_access",
  "organization_id": "00000000-...",
  "refresh_token": "ef34ab12..."
}
```

`scope` lists what you granted. If `invoices:approve` is missing, you didn’t tick approving on the consent screen: run steps 3 and 4 again and tick it.

Save the tokens:

```bash
export ACCESS_TOKEN="ab12cd34..."
export REFRESH_TOKEN="ef34ab12..."
export ORG_ID="00000000-..."
```

## 5. Read your organization

Confirm the connection works, read the base currency, and check that you’re talking to the sandbox:

```bash
curl https://app.accountfun.com/api/v1/organisation \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

The response includes a sandbox marker:

```json
{
  "organization": {
    "id": "00000000-...",
    "name": "My Sandbox",
    "baseCurrency": "USD",
    "sandbox": true
  }
}
```

Stop if sandbox is not true

The sandbox shares its base URL with live organizations, so live credentials left in a test config look identical to sandbox ones. Any test suite that mutates data — including everything from here on — should assert `"sandbox": true` first and refuse to run otherwise.

## 6. Find a contact and an income account

You need a contact ID and an income account ID to create an invoice. The demo seed has both — fetch them:

```bash
# A customer to bill
curl "https://app.accountfun.com/api/v1/contacts?type=customer&limit=5" \
  -H "Authorization: Bearer $ACCESS_TOKEN"


# An income account for the line item
curl https://app.accountfun.com/api/v1/chart-of-accounts \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Pick a `contactId` from the contacts list and an `accountId` from an account where `type` is `Revenue`, `Sales`, or `OtherIncome`.

```bash
export CONTACT_ID="00000000-..."
export ACCOUNT_ID="00000000-..."
```

## 7. Create an invoice

```bash
curl -X POST https://app.accountfun.com/api/v1/invoices \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"contactId\": \"$CONTACT_ID\",
    \"issueDate\": \"2026-08-12\",
    \"dueDate\": \"2026-09-11\",
    \"lines\": [
      {
        \"description\": \"API test invoice\",
        \"quantity\": \"1\",
        \"unitAmount\": \"99.00\",
        \"accountId\": \"$ACCOUNT_ID\"
      }
    ]
  }"
```

A `201 Created` response with the new draft invoice means you’re connected and writing data. The invoice appears as a `DRAFT` inside the sandbox. You can approve it over the API to post it straight to the books — this uses the `invoices:approve` scope you ticked in step 3. Copy the `id` from the `201` response and call the approve endpoint:

```bash
curl -X POST https://app.accountfun.com/api/v1/invoices/INVOICE_ID/approve \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Approving records the revenue and, if the line references a tracked item, updates stock and cost of goods sold automatically. See [Invoices](https://www.accountfun.com/docs/api/invoices/) for the full flow — including creating an invoice already approved in a single call (`"status": "APPROVED"`).

## 8. Reset the sandbox

Finish the loop by returning the sandbox to its fresh demo seed:

```bash
curl -X POST https://app.accountfun.com/api/v1/sandbox/reset \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

A `200 OK` confirms the reset:

```json
{
  "organizationId": "00000000-...",
  "resetAt": "2026-08-31T12:00:00.000Z",
  "status": "reset-to-fresh-seed"
}
```

All data in the sandbox — including the invoice you just created — is wiped and the demo dataset is reseeded. Your token still works: reset never revokes grants or tokens, so a follow-up `GET /api/v1/invoices` lists the fresh demo invoices. You can reset up to 10 times per hour; see [Sandbox](https://www.accountfun.com/docs/api/sandbox/) for reset semantics and limits.

## 9. Refresh the token (later)

Access tokens expire after 30 minutes. When yours does, use the refresh token to get a new pair:

```bash
curl -X POST https://app.accountfun.com/api/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=refresh_token \
  -d refresh_token="$REFRESH_TOKEN" \
  -d client_id="$CLIENT_ID" \
  -d client_secret="$CLIENT_SECRET"
```

Update your stored refresh token

The response contains a **new** `refresh_token`. The old one is now dead — replace it in your storage immediately. Reusing the old token will invalidate the entire grant family.

## Going live

When your integration is ready for real data, run the authorize flow again and pick a live organization in the consent picker instead of the sandbox. Everything else — base URL, endpoints, scopes, token handling — is identical. The `"sandbox"` field on `GET /api/v1/organisation` tells you which kind of organization a token is bound to.

## Where to go next

* **[Sample app](https://www.accountfun.com/docs/api/sample-app/)** — a runnable Node.js or Python integration that does everything above in real code.
* **[Sandbox](https://www.accountfun.com/docs/api/sandbox/)** — the full sandbox lifecycle, lockdown guarantees, and limits.
* **[Authentication](https://www.accountfun.com/docs/api/authentication/)** — the complete OAuth reference, including PKCE and revocation.
* **[Invoices](https://www.accountfun.com/docs/api/invoices/)**, **[Contacts](https://www.accountfun.com/docs/api/contacts/)** — full endpoint reference.
* **[Errors & rate limits](https://www.accountfun.com/docs/api/errors-and-limits/)** — build resilient error handling.
