# Make your first API request

> Create read-only service-account access, exchange it for a short-lived token, and verify a bounded workflow-list response.

Authenticate and list the workflows visible to a workspace-scoped service
account. The request is read-only and explicitly limited to ten results, and an
empty list is a successful first result.

## What you will do

1. Select the workspace the integration should use.
2. Create a service account with only the `api_read` scope and an active run-as
   member.
3. Collect its client credentials without putting the secret in shell history.
4. Exchange those credentials at `POST /api/v1/oauth/token` and inspect the
   complete successful token response.
5. Call `GET /api/json/v1/workflows?page[limit]=10` and verify the body and
   response headers.

This path starts with JSON:API because it works in an empty workspace. GraphQL
warehouse queries require an existing data model and records; webhook ingestion
requires an active webhook workflow and its selected service account.

## Prerequisites

- **Application access:** the base URL of your Hodoflow deployment. The examples
  use `https://hodoflow.com`; self-hosted installations substitute their own
  application host.
- **Setup role:** an organization owner or admin can create the service account.
- **Run-as access:** at least one active organization member that should own the
  API request's permissions.
- **Workspace:** know which workspace the integration should see. Service
  accounts are created in the workspace currently selected in the application.
- **Command line:** Bash, cURL, and `jq`. Confirm them with `curl --version`,
  `jq --version`, and `bash --version`.

Use a private, disposable terminal session. Do not put a client secret or bearer
token in a URL, command-line argument, source file, `.env` file, shell history,
browser storage, screenshot, or support message. Use your normal secret manager
for a real integration.

## Request

### 1. Create read-only access in the intended workspace

In the application, switch to the workspace the integration should use. Then go
to **Settings → Service Accounts**, choose **New Service Account**, and set:

- **Name:** a specific integration name, such as `Reporting sync`;
- **Scopes:** select **API read** only; and
- **Run as:** select the intended active organization member.

Choose **Create Service Account**. You should land on **Client Credentials** and
see a client ID beginning with `cid_` and a client secret beginning with
`csec_`. The workspace is not sent in an API request: it comes from the service
account you just created. The run-as member still bounds everything the account
can see.

For the product-side setup and permission model, see
[Service accounts](/help/credentials-connections/service-accounts). If no active member appears,
activate or invite the intended member before continuing.

### 2. Set variables without recording the secret in history

Set the application URL, then read the credentials interactively. `read -s`
keeps the secret off the screen and out of the command text stored by Bash.

```bash
export HODOFLOW_BASE_URL="https://hodoflow.com"

read -r -p "Client ID: " CLIENT_ID
read -r -s -p "Client secret: " CLIENT_SECRET
printf '\n'
export CLIENT_ID CLIENT_SECRET
```

Paste only when each prompt is waiting. Do not replace the `read` commands with
literal credential assignments. Your current shell and its child processes can
use these values, so do this on a trusted machine and clean them up at the end.

You should see the client ID as you type it and no characters while entering the
secret.

### 3. Exchange the credentials and inspect the response

Build the JSON from environment variables and stream it to cURL. The secret is
in the request body, never the URL or a process argument.

```bash
TOKEN_RESPONSE="$(
  jq -n '{
    grant_type: "client_credentials",
    client_id: env.CLIENT_ID,
    client_secret: env.CLIENT_SECRET
  }' |
    curl --silent --show-error \
      --request POST \
      --header "Content-Type: application/json" \
      --data-binary @- \
      "$HODOFLOW_BASE_URL/api/v1/oauth/token"
)"

printf '%s\n' "$TOKEN_RESPONSE" | jq .
```

You should see all three success fields before extracting the token:

```json
{
  "access_token": "<redacted JWT>",
  "token_type": "bearer",
  "expires_in": 900
}
```

`expires_in` is seconds and defaults to 900, but deployments can configure it.
There is no refresh token. A production client should cache the bearer token for
slightly less than the returned lifetime, then exchange again.

Extract the two values only after verifying that response:

```bash
TOKEN="$(printf '%s\n' "$TOKEN_RESPONSE" | jq -er '.access_token')"
EXPIRES_IN="$(printf '%s\n' "$TOKEN_RESPONSE" | jq -er '.expires_in')"
printf 'Token received; expires in %s seconds.\n' "$EXPIRES_IN"
```

The command prints only the lifetime, not the bearer token.

### 4. List at most ten workflows

Use `-i` to include the response headers and `-g` to keep cURL from treating the
square brackets in `page[limit]` as a URL glob. The bearer header is streamed to
cURL through standard input so it is not placed in the command's arguments.

```bash
printf 'header = "Authorization: Bearer %s"\n' "$TOKEN" |
  curl --silent --show-error --include --globoff --config - \
    --header "Accept: application/vnd.api+json" \
    "$HODOFLOW_BASE_URL/api/json/v1/workflows?page[limit]=10"
```

You should see `HTTP/… 200`, `x-api-version: v1`, a non-empty
`x-request-id`, and a JSON:API document with a `data` array. Keep the request ID
when troubleshooting; it is the correlation ID support can trace.

## Expected response

A new or empty workspace returns `200 OK`. The exact request URL is retained in
the `self` link; the request ID is different for every request.

```http
HTTP/2 200
content-type: application/vnd.api+json; charset=utf-8
x-api-version: v1
x-request-id: <request-id>
```

```json
{
  "data": [],
  "jsonapi": {
    "version": "1.0"
  },
  "links": {
    "first": "https://hodoflow.com/api/json/v1/workflows?page[limit]=10",
    "next": null,
    "prev": null,
    "self": "https://hodoflow.com/api/json/v1/workflows?page[limit]=10"
  },
  "meta": {
    "page": {}
  }
}
```

`data: []` means authentication, workspace selection, scope enforcement, and
serialization all succeeded; it does not mean the request failed. If workflows
already exist, `data` contains up to ten workflow resource objects instead.

## Troubleshooting

### Token exchange returns 400

The only accepted grant is `client_credentials`. Confirm the request body has
that exact `grant_type`. This response is not retryable until the request is
corrected.

### Token exchange or API request returns 401

For the exchange, re-copy the client ID and secret and confirm the service
account is active. For the API call, the token may have expired: exchange again
and retry the read once. Do not log either credential while diagnosing it.

### The API returns 403

The service account is authenticated but lacks `api_read`. An owner or admin
must edit the account and grant **API read**. Retrying the same token cannot add
a scope.

### The API returns 404

Check the exact path: `/api/json/v1/workflows`. A resource-specific `404` can
also mean the run-as member is not allowed to see that object; Hodoflow does not
reveal whether a filtered object exists. Do not retry unchanged.

### The API returns 429

Read the `Retry-After` response header, wait that many seconds, and retry the
read. Keep the `x-request-id` from the failed response. Do not use a fixed rapid
retry loop.

### The response is HTML or has no version header

Confirm `HODOFLOW_BASE_URL` is the application host, not the developer
documentation host, and that the path includes `/api/json/v1`.

Before leaving the terminal, remove every sensitive variable:

```bash
unset CLIENT_ID CLIENT_SECRET TOKEN TOKEN_RESPONSE EXPIRES_IN
```

## Next steps

- [Inspect workflows and follow a run](/platform/json-api) with the same
  `api_read` access.
- Add `api_write` only when you are ready to implement an idempotent JSON:API
  write; keep read-only and write integrations separate where practical.
- [Run a bounded GraphQL warehouse query](/graphql) after you have a data model
  and records. `factQuery` also requires `api_read`.
- [Configure webhook ingestion](/webhook-ingestion) after creating and
  activating a webhook workflow; its trigger requires the exact selected
  service account with the `webhooks` scope.
