> ## Documentation Index
> Fetch the complete documentation index at: https://crevio.co/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Conventions

> The request and response rules every Crevio endpoint follows — unwrapped params, association ids, creation order, money in cents, pagination, and more.

**The Crevio API is Stripe-shaped, with a few rules that trip up every new integration on day one. Learn them here and the rest of the API is predictable.**

These conventions hold across all of `https://api.crevio.co/v1`. Read this page before writing more than a quickstart call — most `400`/`404`/`422` errors come from breaking one of the rules below.

## Parameters are unwrapped (top-level)

Send request fields at the **top level** of the JSON body. There is **no** resource wrapper key. A nested wrapper like `{"product": {...}}` is rejected with a `400 parameter_unknown` error naming the wrapper — see [Errors](/docs/developer/guides/errors#400--parameter_unknown).

```bash theme={null}
# Do — fields at the top level
curl https://api.crevio.co/v1/products \
  -H "Authorization: Bearer $CREVIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Intro to Watercolor", "status": "draft" }'
```

```bash theme={null}
# Don't — wrapped
curl https://api.crevio.co/v1/products \
  -H "Authorization: Bearer $CREVIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "product": { "name": "Intro to Watercolor" } }'
```

## Associations are a bare name + a prefixed id

To link a resource, use the **bare resource name** as the key and pass the **prefixed id string** as the value. Do **not** append `_id`.

```jsonc theme={null}
// Do
{ "product": "prod_abc123" }
{ "customer": "cus_abc123" }
{ "order": "ord_abc123" }
{ "discount": "disc_abc123" }
{ "price_variant": "price_abc123" }
```

```jsonc theme={null}
// Don't — these keys are not recognized
{ "product_id": "prod_abc123" }
{ "customer_id": "cus_abc123" }
```

<Warning>
  **Exceptions exist — match the schema field name exactly.** A few fields keep the `_id` suffix because they take ids directly: `site_id` (e.g. on Tasks), `tag_ids` (an array, e.g. on Customers and Forms), `account_ids`, and `upload_ids`. The rule of thumb is "bare name", but check the [API reference](/docs/developer/api-reference/introduction) for the endpoint you're calling.
</Warning>

## Creation order matters

Some resources have prerequisites. The big one: **a product can't be published without at least one price variant.** Creating a product with `status: "active"` in a single call fails with `422`.

<Steps>
  <Step title="Create the product as a draft">
    `POST /products` with `status: "draft"`.
  </Step>

  <Step title="Create one or more price variants">
    `POST /price_variants` with `product: "prod_..."`.
  </Step>

  <Step title="Publish">
    `PATCH /products/{id}` with `status: "active"`.
  </Step>
</Steps>

## Money is in cents; currency is lowercase

All monetary amounts are **integers in the smallest currency unit** (cents for USD). Currency codes are **lowercase** ISO 4217.

```jsonc theme={null}
{ "amount": 4900, "currency": "usd" }   // $49.00 USD
{ "amount": 49.00, "currency": "USD" }  // wrong — not a float, not uppercase
```

## List responses vs single objects

**List endpoints** return a consistent envelope. The items are in `data`.

```json theme={null}
{
  "object": "list",
  "data": [
    { "id": "prod_abc123", "object": "product", "name": "My Course" },
    { "id": "prod_def456", "object": "product", "name": "Templates Pack" }
  ],
  "has_more": true,
  "url": "/v1/products"
}
```

**Single-resource endpoints** return the object directly (no envelope):

```json theme={null}
{ "id": "prod_abc123", "object": "product", "name": "My Course" }
```

## Pagination

Cursor-based. Pass `limit` (1–100, default 15) and `starting_after` (the `id` of the last item you received). When `has_more` is `false`, you're on the last page.

```bash theme={null}
# First page
curl "https://api.crevio.co/v1/products?limit=10" \
  -H "Authorization: Bearer $CREVIO_API_TOKEN"

# Next page — cursor is the last id from the previous page
curl "https://api.crevio.co/v1/products?limit=10&starting_after=prod_def456" \
  -H "Authorization: Bearer $CREVIO_API_TOKEN"
```

## Metadata

Products, customers, orders, checkouts, checkout links, and sites carry a `metadata` object — key-value storage that's yours, not ours. Use it to store your own system's IDs, campaign labels, or anything else you need to correlate Crevio objects with the rest of your stack. Crevio never interprets it.

* Up to 50 keys per object; keys max 40 characters, values stored as strings up to 500 characters.
* Updates merge key by key. Set a key to `null` (or `""`) to remove it.
* Metadata set on a checkout is copied to the order it produces, and it's echoed in webhook payloads — so an `order.paid` webhook already carries your correlation IDs.
* List endpoints filter by it: `?metadata[campaign]=spring` (all pairs must match).

```bash theme={null}
# Tag a customer with your CRM id
curl -X POST https://api.crevio.co/v1/customers \
  -H "Authorization: Bearer $CREVIO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email": "jane@example.com", "metadata": {"crm_id": "12345", "source": "webinar"}}'

# Find them again by it
curl "https://api.crevio.co/v1/customers?metadata[crm_id]=12345" \
  -H "Authorization: Bearer $CREVIO_API_TOKEN"
```

## Slug lookup

Products, blog posts, experiences, and legal pages accept **either an id or a slug** in the `{id_or_slug}` path position.

```bash theme={null}
curl https://api.crevio.co/v1/products/prod_abc123   # by id
curl https://api.crevio.co/v1/products/intro-to-watercolor   # by slug
```

## Expanding related resources

By default, related resources appear as bare id strings. Pass `expand` to inline them. Multiple fields are comma-separated.

```bash theme={null}
curl "https://api.crevio.co/v1/orders/ord_abc123?expand=customer,line_items" \
  -H "Authorization: Bearer $CREVIO_API_TOKEN"
```

```jsonc theme={null}
// Without expand
{ "id": "ord_abc123", "object": "order", "customer": "cus_xyz789" }

// With ?expand=customer
{
  "id": "ord_abc123",
  "object": "order",
  "customer": { "id": "cus_xyz789", "object": "customer", "email": "jane@example.com" }
}
```

## Delete responses

Delete endpoints return a confirmation object, not an empty body:

```json theme={null}
{ "id": "disc_abc123", "object": "discount", "deleted": true }
```

## The object discriminator

Every resource carries an `object` field identifying its type. Use it to branch on heterogeneous responses.

```json theme={null}
{ "id": "cus_abc123", "object": "customer", "email": "jane@example.com" }
```

## ID prefixes

Every id is a prefixed string encoding its type. Use these — never raw database ids.

| Resource         | Prefix   | `object` value     |
| ---------------- | -------- | ------------------ |
| Account          | `acct_`  | `account`          |
| Product          | `prod_`  | `product`          |
| Price variant    | `price_` | `price_variant`    |
| Experience       | `exp_`   | `experience`       |
| Order            | `ord_`   | `order`            |
| Order item       | `ordi_`  | `order_item`       |
| Charge           | `ch_`    | `charge`           |
| Customer         | `cus_`   | `customer`         |
| Checkout         | `co_`    | `checkout`         |
| Checkout link    | `cl_`    | `checkout_link`    |
| Invoice          | `inv_`   | `invoice`          |
| Subscription     | `sub_`   | `subscription`     |
| Discount         | `disc_`  | `discount`         |
| Blog post        | `post_`  | `blog_post`        |
| Review           | `rev_`   | `review`           |
| File             | `file_`  | `file`             |
| Form             | `form_`  | `form`             |
| Tag              | `tag_`   | `tag`              |
| Task             | `task_`  | `task`             |
| Task run         | `trun_`  | `task_run`         |
| Site             | `site_`  | `site`             |
| Webhook endpoint | `wh_`    | `webhook_endpoint` |
| Webhook event    | `whev_`  | `webhook_event`    |

<Note>
  Courses and other content live under `/experiences`, not `/products`. A product bundles experiences through its price variants. The Experiences API is read-only (list + retrieve).
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Errors" icon="triangle-exclamation" href="/docs/developer/guides/errors">
    Every status code, what it means, and how to debug it.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/docs/developer/guides/quickstart">
    Apply these rules in a real product creation.
  </Card>

  <Card title="API reference" icon="code" href="/docs/developer/api-reference/introduction">
    Per-endpoint parameters and schemas.
  </Card>

  <Card title="TypeScript SDK" icon="npm" href="/docs/developer/guides/sdk">
    The SDK enforces most of these conventions for you.
  </Card>
</CardGroup>
