---
title: "BEEPEC Orders API: connect your system to BEEPEC"
description: "Developer guide to the BEEPEC Orders API: authenticate with an API key, send editing orders from your own software, track their status and collect delivery links automatically."
url: "https://beepec.com/portalkb/beepec-orders-api-connect-your-system-to-beepec/"
updated: "2026-10-22"
category: "API & Integrations"
---

# BEEPEC Orders API: connect your system to BEEPEC

The BEEPEC Orders API is a REST API that lets your own software (a booking system, a CRM, a photography business platform or an in-house tool) work with BEEPEC directly, without anyone placing orders by hand:

- **Place orders**: one request per property, with the address, the services you need, a link to your files and notes for the editor.
- **Track them**: check an order's status at any time, or sync every order that changed since your last check.
- **Collect deliveries**: pick up the delivery link as soon as the edited files are ready.

Orders placed through the API are the same as orders placed in the [BEEPEC client portal](https://portal.beepec.com/). They appear under **My Orders**, your team receives the usual emails, and they are billed on your account's normal terms.

## 1. Before you start

1. **Have a BEEPEC client account.** If you don't have one yet, [create an account](https://portal.beepec.com/) first.
2. **Request an API key.** Email **contact@beepec.com** from your account's email address and ask for API access. We issue one key per integration.
3. **Store the key securely.** The key looks like `bpk_live_…` and is shown to you only once. We keep only a fingerprint of it, so we can't show it again. If it's lost or exposed, ask us to revoke it and issue a new one.

> **Keep the key on your server.** Anyone holding the key can place orders on your account. Store it as a secret or environment variable in your backend. Never put it in web-page code, a browser extension, a mobile app or a public code repository.

## 2. Base URL and authentication

All endpoints live under this base URL:

```
https://drckjnvybgqeaegvljlf.supabase.co/functions/v1/client-api/v1
```

Send your key on every request as a bearer token:

```
Authorization: Bearer bpk_live_your_key_here
```

**Check your setup** by requesting your account details:

```
curl https://drckjnvybgqeaegvljlf.supabase.co/functions/v1/client-api/v1/account 
  -H "Authorization: Bearer $BEEPEC_API_KEY"
```

```
{
  "name": "Your Company",
  "currency": "USD",
  "billing_cadence": "weekly",
  "can_order": true,
  "credit": { "status": "ok", "outstanding": 0, "limit": 1000 }
}
```

`can_order` is `false` while unpaid invoices have reached your account's credit limit. Pay them in the portal and ordering resumes.

### Conventions

- HTTPS only. Requests and responses are JSON in UTF-8.
- Times are ISO-8601 in UTC, for example `2026-10-18T02:04:28Z`.
- The API is versioned in the path (`/v1`). Within version 1 we may add new fields to responses, but we never remove or rename existing ones, so ignore fields you don't use.

## 3. Endpoints

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/v1/account` | Your account name, currency, billing terms and credit status |
| GET | `/v1/services` | The services you can order, with your price per unit and usual turnaround |
| POST | `/v1/orders` | Place an order |
| GET | `/v1/orders/{order_number}` | One order, with its status, delivery and invoice |
| GET | `/v1/orders` | Your orders, most recent first, with filters for syncing |

## 4. Services

| `service` code | What it covers | Quantity counts |
| --- | --- | --- |
| `photo_editing` | Single/HDR editing, pano stitching and editing | photos |
| `photo_retouching` | Object removal, room clean-up, season conversion, lawn replacement | photos |
| `floor_plan` | 2D black/white, 2D full color, 2D texture, 3D full color | plans |
| `virtual_staging` | Virtual staging, renovation | rooms |
| `video_editing` | Custom video, virtual timelapse, reels | videos |
| `virtual_twilight` | Twilight conversion, warm sky, lit windows | exteriors |

For retouching, our QC team reviews the photos and sets the exact level (basic to extreme) before invoicing, so you don't need to choose one. `GET /v1/services` returns this list with your account's starting price per unit (`unit_price_usd`) and the usual turnaround in hours (`turnaround_hours`).

## 5. Placing an order

`POST /v1/orders`

```
curl -X POST https://drckjnvybgqeaegvljlf.supabase.co/functions/v1/client-api/v1/orders 
  -H "Authorization: Bearer $BEEPEC_API_KEY" 
  -H "Content-Type: application/json" 
  -d '{
    "external_ref": "shoot-1042",
    "name": "123 Example Street, Springfield",
    "services": [
      { "service": "photo_editing", "quantity": 25 },
      { "service": "virtual_twilight", "quantity": 1 }
    ],
    "input_links": [
      { "url": "https://www.dropbox.com/scl/fo/example?dl=0", "label": "RAW files" }
    ],
    "instructions": "Please remove the realtor sign from the front lawn."
  }'
```

| Field | Required | Rules |
| --- | --- | --- |
| `name` | yes | The property address or job name, up to 200 characters. |
| `services` | yes | 1–10 entries, each service at most once. `quantity` is a whole number from 1 to 1000. |
| `input_links` | yes | 1–10 links to your files. Each `url` must start with `https://` (Dropbox, Google Drive, WeTransfer and other file-sharing links work). Make sure anyone with the link can view the files. `label` is optional, up to 100 characters. |
| `instructions` | no | Plain text for the editor, up to 5,000 characters. Line breaks are kept; HTML is shown as text. |
| `external_ref` | recommended | Your own ID for this job (for example your shoot or booking ID), up to 100 printable characters. See "Safe retries" below. |

**Response:** `201 Created`

```
{
  "order": {
    "order_number": "JOB-12345",
    "external_ref": "shoot-1042",
    "name": "123 Example Street, Springfield",
    "status": "submitted",
    "services": [
      { "service": "photo_editing", "name": "Single/HDR", "quantity": 25 },
      { "service": "virtual_twilight", "name": "Virtual Twilight", "quantity": 1 }
    ],
    "created_at": "2026-10-18T02:04:28Z",
    "deadline": "2026-10-18T14:04:28Z",
    "delivered_at": null,
    "delivery": null,
    "invoice": null,
    "portal_url": "https://portal.beepec.com/app/all-jobs?job=JOB-12345"
  }
}
```

Save `order_number`. It's how you look the order up later, and it's the number to quote when you contact us.

### Safe retries

Always send `external_ref`. If a request times out or you're not sure it arrived, send **exactly the same request again**:

- If we already have the order, you get `200 OK` with the existing order. Nothing is created twice.
- If you reuse an `external_ref` with **different** details, you get `409 external_ref_conflict`. Use a new reference for a new job.

Without `external_ref`, an identical order sent again within one minute is refused with `409 duplicate_order`, to protect you from double submissions.

## 6. Tracking an order

`GET /v1/orders/{order_number}` returns `{ "order": … }` in the same shape as above.

| `status` | Meaning |
| --- | --- |
| `submitted` | Received, waiting to start |
| `processing` | Being edited or checked by our QC team |
| `revision_requested` | A revision is in progress |
| `on_hold` | Paused; we'll contact you |
| `completed` | Delivered |
| `cancelled` | Cancelled |

When the order is delivered, `delivery` is filled in:

```
"delivery": {
  "available": true,
  "payment_required": false,
  "links": [{ "label": "Edited photos", "url": "https://www.dropbox.com/…" }]
}
```

- If the files were uploaded to the portal instead of shared as a link, download them from `portal_url`.
- If your account pays before downloading, `available` is `false` and `links` is empty until the invoice is paid.
- `invoice` shows the invoice `number`, `status` (`pending`, `paid` or `overdue`) and `amount_usd` once an invoice exists.

## 7. Listing and syncing orders

`GET /v1/orders` returns your orders, most recent first, including orders placed in the portal.

| Query parameter | Example | Meaning |
| --- | --- | --- |
| `status` | `completed` | Only orders with this status |
| `external_ref` | `shoot-1042` | Find an order by your own ID |
| `updated_since` | `2026-10-18T00:00:00Z` | Only orders that changed at or after this time |
| `limit` | `100` | Page size, 1–100 (default 20) |
| `offset` | `100` | Skip this many orders, for paging |

```
{ "orders": [ … ], "page": { "limit": 20, "offset": 0, "has_more": true } }
```

**Recommended sync pattern.** The API doesn't push notifications, so check for changes on a schedule:

1. Every 15–30 minutes, call `GET /v1/orders?updated_since=<time of your last successful sync>&limit=100`.
2. Page with `offset` while `has_more` is `true`.
3. For each order with `status: "completed"`, save `delivery.links` against your record (match it by `external_ref` or `order_number`).
4. Store the time at which this sync started, and use it as `updated_since` next time.

We also email delivery links to your account as usual, so your team is informed even if you don't sync.

## 8. Errors

Every error has the same shape:

```
{
  "error": {
    "code": "validation_failed",
    "message": "services[0].service: unknown_service",
    "details": { "services[0].service": "unknown_service" }
  }
}
```

| HTTP | `code` | What to do |
| --- | --- | --- |
| 400 | `invalid_json` | The body isn't valid JSON. |
| 401 | `missing_api_key`, `invalid_api_key` | Check the `Authorization` header and your key. |
| 402 | `credit_limit_reached` | Unpaid invoices have reached your account's limit. Pay them in the portal, then retry. |
| 403 | `account_inactive` | Contact us. |
| 404 | `order_not_found`, `not_found` | Check the order number or the path. |
| 405 | `method_not_allowed` | Wrong HTTP method for this path. |
| 409 | `duplicate_order`, `external_ref_conflict` | See "Safe retries". |
| 422 | `validation_failed` | Fix the fields listed in `details`. Don't retry the same request unchanged. |
| 429 | `order_limit_reached` | Your key's daily order limit (50 by default) was reached. Contact us if you need more. |
| 500, 503 | `internal_error`, `service_unavailable` | Temporary. Wait a little and retry with the same `external_ref`. |

**Retry only** network errors, timeouts and `5xx` responses, with an increasing pause between attempts (for example 2, 4, then 8 seconds), and always with the same `external_ref`. Don't retry `4xx` responses without fixing the cause first.

## 9. Code examples

### JavaScript (Node.js 18 or later, server-side)

```
// Keep the key in an environment variable on your server, never in browser code.
const BASE = "https://drckjnvybgqeaegvljlf.supabase.co/functions/v1/client-api/v1";

export async function placeBeepecOrder(job) {
  const res = await fetch(`${BASE}/orders`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BEEPEC_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      external_ref: job.id,                 // your own ID: makes retries safe
      name: job.address,
      services: job.services,               // e.g. [{ service: "photo_editing", quantity: 25 }]
      input_links: [{ url: job.filesUrl, label: "RAW files" }],
      instructions: job.notes ?? "",
    }),
  });
  const body = await res.json();
  if (!res.ok) {
    // body.error.code says why. Timeouts and 5xx are safe to retry with the same external_ref.
    throw new Error(`BEEPEC ${res.status} ${body.error?.code}: ${body.error?.message}`);
  }
  return body.order.order_number;           // e.g. "JOB-12345"
}
```

### Python (requests)

```
import os
import requests

BASE = "https://drckjnvybgqeaegvljlf.supabase.co/functions/v1/client-api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['BEEPEC_API_KEY']}"}

def orders_changed_since(since_iso):
    """Every order that changed since the last sync, across all pages."""
    orders, offset = [], 0
    while True:
        r = requests.get(f"{BASE}/orders", headers=HEADERS, timeout=30,
                         params={"updated_since": since_iso, "limit": 100, "offset": offset})
        r.raise_for_status()
        page = r.json()
        orders.extend(page["orders"])
        if not page["page"]["has_more"]:
            return orders
        offset += 100

for order in orders_changed_since("2026-10-18T00:00:00Z"):
    if order["status"] == "completed" and order["delivery"] and order["delivery"]["available"]:
        print(order["order_number"], order["external_ref"], [l["url"] for l in order["delivery"]["links"]])
```

### Low-code platforms

Automation tools and app builders (for example Zapier, Make, n8n or an app builder's backend functions) can use the API through their HTTP request step. Put the key in the platform's secret or credential store, call the API from a server-side step, and never from a page that runs in the browser.

## 10. Security checklist

- Keep the API key server-side, in a secret store or environment variable.
- Use one key per integration, so a key can be revoked without affecting anything else.
- Never log the full key. Log the `order_number` and `error.code` instead.
- If a key may have been exposed, email us straight away and we'll revoke it and issue a new one.

## Help

Email **contact@beepec.com** with your `order_number`, or the `error.code` and `message` you received, and we'll help you.
