Skip to content

Authentication

Every REST API request carries an API key as a bearer token. Keys belong to your workspace, act as the person who created them, and are either read-only or read-write.

GET /api/v1/entries HTTP/1.1
Host: api.prophouse.dev
Authorization: Bearer ph_live_xxxxxxxxxxxx

Open API keys in the account dashboard at https://app.prophouse.dev/keys, enter a Name, pick a Scope, and click Create key. The full key is shown once; copy it then. The dashboard lists each key with its prefix, scope and last-used time, and Revoke disables it on the next request. See API keys for the dashboard side.

Hosted keys start with ph_live_. Treat them like passwords: keep them out of source control and pass them through an environment variable or a secret store.

Scope What it allows
read Every GET, plus POST /entries/search/image (search by image is a read that needs a request body). Search, browse, download, read comments, reviews and activity.
write Everything read allows, plus every other POST, PUT, PATCH and DELETE: pushing, editing, commenting, reviewing, creating pull jobs, check-out locks.

A read-scoped key that attempts a write gets 403 with the message This key is read-only. …. Write scope is available on every plan.

A request made with a key acts as the person who created the key. Comments, check-outs, pushes and review decisions are attributed to that person, and the key carries that person’s current role in the workspace:

Workspace role In the API
Owner or admin Admin. Can call endpoints marked Admin in this reference (webhooks, denylist, entry deletion, deprecation).
Member Regular member. Admin-only endpoints return 403 FORBIDDEN.

Role changes apply on the next request. If the creator leaves the workspace, every key they created stops working with 401.

To find out who a key is, call GET /auth/me:

{
"user": {
"id": "<account_id>",
"name": "Ada Artist",
"email": "ada@example.com",
"display_name": "Ada Artist",
"role": "admin"
},
"name": "Ada Artist",
"org_id": "<workspace_id>",
"org": { "id": "<workspace_id>", "name": "Northwind Games", "slug": "northwind" },
"scope": "write"
}

role is admin for workspace owners and admins, otherwise member. The user.id here is your account id. Payloads inside the library (comment authors, lock holders, reviewers) use library-local numeric user ids; call GET /members/me to get yours.

If you are building an app or command-line tool that people sign into, use device authorization (RFC 8628 style) instead of asking them to paste a key. The app gets its own key, visible and revocable in the dashboard like any other.

  1. Request a code. No authentication is needed.

    Terminal window
    curl -X POST https://api.prophouse.dev/api/v1/auth/device/code \
    -H "Content-Type: application/json" \
    -d '{"client_name": "Asset sync script", "scopes": "write"}'
    {
    "device_code": "phd_...",
    "user_code": "WXYZ-BCDF",
    "verification_uri": "/device",
    "verification_uri_complete": "/device?code=WXYZ-BCDF",
    "expires_in": 900,
    "interval": 2
    }

    client_name (up to 80 characters) is what the person sees when approving and becomes part of the key’s name. scopes is read (the default) or write.

  2. Show the person the user_code and open https://api.prophouse.dev followed by verification_uri_complete in their browser. The URIs are relative on purpose. The person signs in to the dashboard, checks the code and approves or denies.

  3. Poll for the key every interval seconds until the code expires (15 minutes).

    Terminal window
    curl -X POST https://api.prophouse.dev/api/v1/auth/device/token \
    -H "Content-Type: application/json" \
    -d '{"device_code": "phd_..."}'

    Once approved, the response carries the key exactly once:

    {
    "token": "ph_live_xxxxxxxxxxxx",
    "token_id": "<key_id>",
    "scope": "write",
    "gateway_url": "https://api.prophouse.dev"
    }

Poll outcomes use the standard error envelope, so switch on error.code:

Status error.code Meaning and what to do
400 DEVICE_PENDING Not approved yet. Keep polling at details.interval seconds.
429 DEVICE_SLOW_DOWN You polled faster than allowed. Wait details.interval seconds before the next poll.
400 DEVICE_EXPIRED The code expired. Start again from step 1.
400 DEVICE_DENIED The person denied the request. Stop.
401 UNAUTHORIZED Unknown device code, or the key was already collected.
400 INVALID_REQUEST The body has no device_code.

Requesting codes is rate limited; too many attempts in a short time make POST /auth/device/code return 429 (retry a few minutes later).

Status When
401 No Authorization header, unknown or revoked key, or the key’s creator is no longer a workspace member.
403 A read-scoped key attempted a write, or a member called an admin-only endpoint.

Authentication failures from the API front door have a short body such as {"error": "Unknown or revoked API key."}. See Errors and rate limits for both error shapes.