NAV
shell

Retail

Prerequisistes

Please read this section first. (Do you want to use our API?)

To be able to use the external api you need a REVO Retail.

  1. Login into the desired account
  2. Go to account management
  3. Create a new token

Authorization

The main URL for the external API:

https://revoretail.works/api/external

The URL for the integrations API environment:

https://integrations.revoretail.works/api/external

And you should provide the mandatory headers for the authentication

Header Value
username {account-username}
Authorization Bearer {token}
Content-Type application/json

Generic list response

{
    "current_page": 1,
    "data": [  [Response data will go here]  ],
    "first_page_url": "https://revoretail.works/api/external/{resource}?page=1",
    "from": 1,
    "last_page": 2,
    "last_page_url": "https://revoretail.works/api/external/{resource}?page=2",
    "next_page_url": "https://revoretail.works/api/external/{resource}?page=2",
    "path": "https://revoretail.works/api/external/{resource}",
    "per_page": 50,
    "prev_page_url": null,
    "to": 50,
    "total": 75
}

Available resources

Variants

RevoRetail uses variants to store product sizes, colors, etc..

It has 3 objects kinds to represent them

** 1. Variant Set ** Variant kind, for example, size or color.

GET https://revoretail.works/api/external/catalog/variantSets

GET variantSets

[
    {"id": 1, "name": "Color"},
    {"id": 1, "name": "Size"}
]

** 2. Variant ** Variants are every variantSet variant, so blue, red, yellow,… for colors or L, XL,… for sizes.

GET https://revoretail.works/api/external/catalog/variants

GET variants

[
   {
        "id": 1,
        "name": "Red",
        "order": 2,
        "variant_set_id": 1,
        "color": null
    },
    {
        "id": 2,
        "name": "Blue",
        "order": 1,
        "variant_set_id": 1,
        "color": null
    },
    {
        "id": 3,
        "name": "L",
        "order": 1,
        "variant_set_id": 2,
        "color": null
    },
    {
        "id": 4,
        "name": "XL",
        "order": 1,
        "variant_set_id": 2,
        "color": null
    }
]

** 3. Product Variants ** Then we have product variants that are variants linked to products.

GET https://revoretail.works/api/external/catalog/productsVariants

GET productsVariants

[
    {
        "id": 1,
        "product_id": 23,
        "variant_1_id": 1,
        "variant_2_id": 3
    },
    {
        "id": 2,
        "product_id": 23,
        "variant_1_id": 2,
        "variant_2_id": 3
    },
    {
        "id": 3,
        "product_id": 25,
        "variant_1_id": 2,
        "variant_2_id": 4
    },
    {
        "id": 4,
        "product_id": 25,
        "variant_1_id": 1,
        "variant_2_id": 4
    }
]

Get list of taxes

Get a list of available taxes

GET https://revoretail.works/api/external/config/taxes

GET taxes

[
    {
        "id": 1,
        "name": "10%",
        "taxPercentage": 10.00
    },
    {
        "id": 2,
        "name": "21%",
        "taxPercentage": 21.00
    }
]

Get stocks list

Get an stock list.

GET https://revoretail.works/api/external/catalog/stocks

GET stocks

[
    {
        "id": 1,
        "item_id": "1",
        "warehouse_id": "1",
        "quantity": 10.00
    },
    {
        "id": 2,
        "item_id": "1",
        "warehouse_id": "2",
        "quantity": 20.00
    },
    {
        "id": 3,
        "item_id": "2",
        "warehouse_id": "2",
        "quantity": 15.00
    }
]

Generate stock movement.

We can generate stock movements with the following POST (required parameters: warehouse id, product id and quantity to move)

POST https://revoretail.works/api/external/stocks/add

POST stocks/add

{
    "product_id": 1,
    "warehouse_id": 1,
    "quantity": 22.5
}

The answer is a 200 JSON with the current warehouse stock quantity.

Create Order

Store an order.

POST https://revoretail.works/api/external/orders

POST orders

{
    "notes": "",
    "shippingAmount": 0,
    "sum": 71.5,
    "subtotal": 59.09,
    "discount": 17.5,
    "orderDiscount": 6.5,
    "tax": 1,
    "total": 65,
    "discountObject": {
        "amount": 1.1,
        "id": null,
        "isPercentage": true,
        "name": "Without iva"
    },
    "customer_id": null,
    "shipping_id": null,
    "employee_id": 1,
    "closed_at": "2017-12-31 13:00:00",
    "order_contents": [
        {
            "name": "Dark shoes XL", 
            "quantity": 2, 
            "weight": null,
            "price": 27,
            "sum": 54,
            "discount": 11,
            "subtotal": 40,
            "tax": 4,
            "total": 44,
            "taxPercentage": 10,
            "discountObject": {
                "amount": 1.1,
                "id": null,
                "isPercentage": false,
                "name": "2nd unit 50%"
            },
            "notes": "",
            "margin": 0,
            "product_id": 1
        }, {
            "name": "Red shoes M", 
            "quantity": 1, 
            "weight": null,
            "price": 27.5,
            "sum": 27.5,
            "discount": 0,
            "subtotal": 25,
            "tax": 2.75,
            "total": 27.5,
            "taxPercentage": 10,
            "discountObject": {},
            "notes": "",
            "margin": 0,
            "product_id": 2
        }
    ]
}

This will return an order. As relevant fields we do get the order id which will be used to make payments and create an invoice.

{"id": 1, "status": 0, ...}

Create an order payment

Create order payments with its invoice.

POST https://revoretail.works/api/external/orders/{order_id}/invoices

POST orders/{order_id}/invoices

{
    "payments": [
    {
        "amount": 65,
        "change": 0,
        "extra": 0,
        "payment_method_id": 1
    }
    ]
}

This will return an order invoice. As new relevant fields we can get the id.

{"id": 1, "number": "E-1", ...}

Get list of Payments Methods

Get a list of available payment methods (card, cash, others...)

GET https://revoretail.works/api/external/config/payment_methods

GET payment_methods

[
    {
        "id": 1,
        "name": "Card",
    },
    {
        "id": 2,
        "name": "Cash",
    }
]

Customers

GET Customers

Get a list of paginated customers

GET https://revoretail.works/api/external/config/customers

GET customers

Response:
{
    "current_page": 1,
    "data": [
        {
            "id": 1,
            "name": "Name",
            "address": "Address",
            "city": "City",
            "state": "State",
            "country": "ES",
            "postalCode": "00008",
            "nif": "12345678A",
            "web": null,
            "email": "test@test.test",
            "phone": "123456789",
            "notes": null,
            "maxCredit": "0.00",
            "extra_id": null,
        }
    ],
    "first_page_url": "https://revoretail.works/api/external/config/customers?page=1",
    "from": 1,
    "last_page": 1,
    "last_page_url": "https://revoretail.works/api/external/config/customers?page=1",
    "next_page_url": null,
    "path": "https://revoretail.works/api/external/config/customers",
    "per_page": 50,
    "prev_page_url": null,
    "to": 1,
    "total": 1
}

POST Customers

Create a customer

POST https://revoretail.works/api/external/config/customers

POST customers

{
    "name": "Name", // required
    "address": "Address", // required
    "city": "City",
    "state": "State",
    "country": "ES",
    "postalCode": "00008",
    "nif": "12345678A",
    "web": null,
    "email": "test@test.test",
    "phone": "123456789",
    "notes": null,
    "maxCredit": "0.00",
    "extra_id": null,
}

Error for required params:

HTTP request status 422
{
    "error": "The given data was invalid."
    "code": 0,
    "data": []
}

PATCH Customers

Update a customer

PATCH https://revoretail.works/api/external/config/customers/{customer_id}

PATCH customers/{customer_id}

{
    "name": "Name Updated"
}

Sync chains

Sync account chains data

POST https://revoretail.works/api/external/chains/sync

Response

{
    "data": {"message": Synced},
}

Retail Catalog

Inside catalog there are many resources: groups, categories and resources.

The api has a limit of 40 requests every minute so the best practice is to cache for x time the fetched information done with the getXXX actions.

Prerequisistes

To be able to use the external api you need a REVO Retail.

  1. Login into the desired account
  2. Go to account management
  3. Create a new token

Authorization

The main URL for the external API:

https://revoretail.works/api/external/{resource}

The URL for the integrations API environment:

https://integrations.revoretail.works/api/external/{resource}

And you should provide the mandatory headers for the authentication

Header Value
username {account-username}
Authorization Bearer {token}
Content-Type application/json

Generic List Response

{
    "current_page": 1,
    "data": [  [Response data will go here]  ],
    "first_page_url": "https://revoretail.works/api/external/{resource}?page=1",
    "from": 1,
    "last_page": 2,
    "last_page_url": "https://revoretail.works/api/external/{resource}?page=2",
    "next_page_url": "https://revoretail.works/api/external/{resource}?page=2",
    "path": "https://revoretail.works/api/external/{resource}",
    "per_page": 50,
    "prev_page_url": null,
    "to": 50,
    "total": 75
}

URL Parameters

You can add the next URL parameters for the paginated responses:

Key Type Required Description
page number optional As the data is paginated, use this parameter to select the page to fetch.
pagination number optional Number of objects per page. The default value is 50 and the max allowed is 200.

At the bottom of the request, it is specified information about the current page and pagination.

General Requirements

Catalog Structure

The main catalog structure is the following:

Groups => Categories => Items

Items cannot exist without Categories, and Categories cannot exists without Groups.

Groups

Can list, show, create, update and delete Groups.

GET api/external/catalog/groups

Response is a groups paginated array with the following fields:

Field Type Required Extra info
id number ReadOnly autoincrement
name string required
photo string optional
order number optional
active boolean optional default: 1
tax_id number optional
tax object ReadOnly

tax object (optional):

Field Type
id number
name string
percentage string
shouldStack boolean

GET api/external/catalog/groups/<group_id>

POST api/external/catalog/groups

PATCH api/external/catalog/groups

Create groups (POST) catalog/groups

[
    {
        "name": "Test Group 1",
        ...
    },
    {
        "name": "Test Group 2",
        ...
    },
    ...
]

Update a group (POST) catalog/groups/<group_id>

{
    "name": "Super Test Group 1",
    ...
}

Massive groups update (PATCH) catalog/groups

[
    {
        "id": 1  
        "name": "Group A updated",
        ...
    },
    {
        "id": 2,
        "name": "Group B updated",
        ...
    }
]

DELETE api/external/catalog/groups/<group_id>

Categories

Can list, show, create, update and delete Categories.

GET api/external/catalog/categories

Response is a groups paginated array with the following fields:

Field Type Required Extra info
id number ReadOnly autoincrement
name string required
group_id number required
photo string optional
order number optional
active boolean optional default: 1
extra_id string optional
tax_id number optional
tax object ReadOnly

tax object (optional):

Field Type
id number
name string
percentage string
shouldStack boolean

GET api/external/catalog/categories/<category_id>

POST api/external/catalog/categories

PATCH api/external/catalog/categories

Create categories (POST) catalog/categories

[
    {
        "name": "Test Category 1",
        "group_id": 1,
        ...
    },
    {
        "name": "Test Category 2",
        "group_id": 1,
        ...
    },
    ...
]

Update a category (POST) catalog/category/<category_id>

{
    "name": "Super Test Category 1",
    ...
}

Massive categories update (PATCH) catalog/categories

[
    {
        "id": 1  
        "name": "Category 1 updated",
        "group_id": 1,
        ...
    },
    {
        "id": 2,
        "name": "Category 2 updated",
        "group_id": 2,
        ...
    }
]

DELETE api/external/catalog/categories/<category_id>

Products

Can list, show, create, update and delete Products.

GET api/external/catalog/products

Response is a groups paginated array with the following fields:

Field Type Required Extra info
id number ReadOnly autoincrement
name string required
category_id number required
photo string optional
order number optional
active boolean optional default: 1
extra_id string optional
reference string optional
info string optional
shortInfo string optional
brand string optional
season string optional
featured boolean optional default: 0
isOpen boolean optional default: 0
weight number optional default: 0
type number optional 0 = NORMAL (default), 1 = KIT, 2 = TICKET, 3 = CONTAINER, 4 = GIFT_CARD, 5 = VARIANT_MASTER, 6 = VARIANT, 7 = MANAGEMENT_ONLY, 8 = VOUCHER
price decimal optional default: 0.00
costPrice decimal optional default: 0.00
barcode string optional default: 0.00
main_product_id number optional
complementary_product_id number optional
discountinued_at date optional YYYY-MM-DD
traceability boolean optional default: 0
usesStockManagement boolean optional default: 0
usesWeight boolean optional default: 0
unit_id number optional
tax_id number optional
tax object ReadOnly
variant_master object ReadOnly Same product structure (optional)
ecommerce_info object ReadOnly Showed with filter withECommerce.
stocks array (objects) ReadOnly Showed with filter withStocks or ECommerce module enabled + filter withECommerceStocks.
variant_info object ReadOnly Showed with filter withVariantInfo or ECommerce module enabled + filter withECommerceStocks..

tax object (optional):

Field Type
id number
name string
percentage string
shouldStack boolean

ecommerce_info object (optional):

Field Type
id number
product_id number
active boolean
weight number
sizes object (width, height and depth)
price string
discount_amount string
discount_percentage string

stocks array (optional):

Field Type
id number
quantity string
alert boolean
warehouse_id number
item_id number
unit_id number

variant_info object (optional):

Field Type
id number
product_id number
variant_1_id number
variant_2_id number

GET api/external/catalog/products/<product_id>

POST api/external/catalog/products

PATCH api/external/catalog/products

Create products (POST) catalog/products

[
    {
        "name": "Test Category 1",
        "category_id": 1,
        ...
    },
    {
        "name": "Test Category 2",
        "category_id": 1,
        ...
    },
    ...
]

Update a product (POST) catalog/products/<product_id>

{
    "name": "Super Test Product 1",
    ...
}

Massive products update (PATCH) catalog/products

[
    {
        "id": 1  
        "name": "Product A",
        "price": 10.99,
        ...
    },
    {
        "id": 33,
        "name": "Product B",
        "category_id": 5,
        ...
    }
]

DELETE api/external/catalog/products/<product_id>

Stocks

Can list and create/update Stocks

GET api/external/catalog/stocks

List Stocks (GET) catalog/stocks

# Response
[
    {
        "quantity": 20,
        "defaultQuantity": 10,
        "alert": 4,
        "warehouse_id": 1,
        "item_id": 1,
        "unit_id": 1
    },
    {
        "quantity": 15,
        "defaultQuantity": 5,
        "alert": 4,
        "warehouse_id": 1,
        "item_id": 2,
        "unit_id": 1
    },
]

POST api/external/stocks/add

Create/Update a Stock (POST) api/external/stocks/add

If the warehouse_id has a product_id already registered, it will update the record. If not, it will create a new stock record.

# Body
{
    "warehouse_id" : 1, // Required
    "product_id" : 1, // Required
    "quantity": 20 // Required
}
# Response
{
    "quantity": 20
}

Price Rates

Price Rates

Can list, create, update and delete price rates.

GET api/external/promotions/priceRates

Returns a list of price rates.

POST api/external/promotions/priceRates

Allows creating a price rate. The request body should include:

Field Type Required Extra info
name string required
type number required 1 = percentage, 2 = price
amount decimal required
rounding number optional default: none, valid values: 1, 2, 3, 4, 5 (none, nearest_05, nearest_99, ceil_99, floor_99)

PUT api/external/promotions/priceRates/{id}

Allows updating an existing price rate. The request body should include:

Field Type Required Extra info
name string required
type number required 1 = percentage, 2 = price
amount decimal required
rounding number optional default: none, valid values: 1, 2, 3, 4, 5 (none, nearest_05, nearest_99, ceil_99, floor_99)

DELETE api/external/promotions/priceRates/{id}

Assigning Products to Price Rates

Can list, create, update, and delete product prices for specific price rates.

GET api/external/promotions/priceRates/{priceRate}/products

Returns a list of product prices for the specified price rate.

POST api/external/promotions/priceRates/{priceRate}/products

Allows assigning/updating products to a price rate. If any of the products sent do not exist, they will not be linked.

The request body should include:

Field Type Required Extra info
prices array required
product_id number required
price decimal required

DELETE api/external/promotions/priceRates/{priceRate}/products/{product}

Allows deleting a specific product from a price rate.

DELETE api/external/promotions/priceRates/{priceRate}/products

Allows deleting multiple products from a price rate. The request body should include:

Field Type Required Extra info
prices array required
product_id number required

Assigning Price Rates to Products

Can list, create, update, and delete price rates for specific products.

GET api/external/catalog/products/{product}/priceRates

Returns a list of price rates for the specified product.

POST api/external/catalog/products/{product}/priceRates

Allows linking/updating price rates to a product. If any of the price rates sent do not exist, they will not be linked.

The request body should include:

Field Type Required Extra info
prices array required
price_rate_id number required
price decimal required

DELETE api/external/catalog/products/{product}/priceRates/{priceRate}

Allows unlinking a specific price rate from a product.

DELETE api/external/catalog/products/{product}/priceRates

Allows unlinking multiple price rates from a product. The request body should include:

Field Type Required Extra info
prices array required
price_rate_id number required

Promotions

Promotions

Can list, create, update, and delete promotions.

GET api/external/promotions/promotions

Returns a list of promotions.

POST api/external/promotions/promotions

Creates a new promotion. The request body should include:

Field Type Required Extra info
id number optional unique: promotions (for updates) - Unique identifier of the promotion
active boolean optional State of the promotion (active or not)
order number optional Priority order of the promotion
name string required Name of the promotion
type number required Type of promotion, value from the enum PromotionType
apply_to number required Object to which the promotion applies, value from the enum PromotionApplyTo
start_date string optional Start date of the promotion (YYYY-MM-DD)
end_date string optional End date of the promotion. Cannot be earlier than the start date (YYYY-MM-DD)
start_time string optional Format: H:i - Start time of the promotion
end_time string optional Format: H:i - End time of the promotion
weekdays array optional Applicable days of the week (values like 1 for Monday, 7 for Sunday)
apply_type number requiredIf:type=1 Possible values: 1 for 50% discount, 2 for cheapest product is free
group_by number optional Possible values: 1 for grouped by category, 2 for grouped by product, 3 for grouped by variant
price_rate_id number requiredIf:type=2 Must be an existing identifier in the price_rates table (id field)
combo_price decimal requiredIf:type=3 min:0 - Combined price, must be greater than or equal to 0
condition number requiredIf:type=4 Possible values: 1 for Condition type 1, 2 for Condition type 2
quantity decimal requiredIf:type=4 min:0 - Minimum quantity required to fulfill the condition
action number requiredIf:type=4 Possible values: 1 for Action type 1, 2 for Action type 2
discount decimal requiredIf:type=4 min:0 - Value of the discount, must be greater than or equal to 0

PUT api/external/promotions/promotions/{promotion}

Updates an existing promotion. The request body should include the same fields as in the POST, but the id field is prohibited.

DELETE api/external/promotions/promotions/{promotion}

Deletes a specified promotion.

Assigning Categories to Promotions

Can list, create, update, and delete categories associated with specific promotions.

GET api/external/promotions/promotions/{promotion}/categories

Returns a list of categories associated with a specific promotion.

POST api/external/promotions/promotions/{promotion}/categories

Allows associating categories with a promotion. The request body should include:

Field Type Required Extra info
categories array required List of existing category IDs to link

DELETE api/external/promotions/promotions/{promotion}/categories/{category}

Deletes a specific category from a promotion.

DELETE api/external/promotions/promotions/{promotion}/categories

Allows unlinking multiple categories from a promotion. The request body should include:

Field Type Required Extra info
categories array required List of category IDs to unlink

Linking Promotions to Products

Can list, create, update, and delete promotions associated with specific products.

GET api/external/products/{product}/promotions

Returns a list of promotions associated with a specific product.

POST api/external/products/{product}/promotions

Allows associating promotions with a product. The request body should include:

Field Type Required Extra info
promotions array required List of promotion IDs to link

DELETE api/external/products/{product}/promotions/{promotion}

Deletes a specific promotion from a product.

DELETE api/external/products/{product}/promotions

Allows unlinking multiple promotions from a product. The request body should include:

Field Type Required Extra info
promotions array required List of promotion IDs to unlink

Linking Promotions to Categories

Can list, create, update, and delete promotions associated with specific categories.

GET api/external/categories/{category}/promotions

Returns a list of promotions associated with a specific category.

POST api/external/categories/{category}/promotions

Allows associating promotions with a category. The request body should include:

Field Type Required Extra info
promotions array required List of promotion IDs to link

DELETE api/external/categories/{category}/promotions/{promotion}

Deletes a specific promotion from a category.

DELETE api/external/categories/{category}/promotions

Allows unlinking multiple promotions from a category. The request body should include:

Field Type Required Extra info
promotions array required List of promotion IDs to unlink

Retail Reports

Authentication

curl --header "username: {account-username}" \
     --header "Authorization: Bearer {token}" \
     --header "Content-Type: application/json" \
     https://revoretail.works/api/external/v3/reports/{reportName}

The main URL for the external API:

https://revoretail.works/api/external/v3/reports/{reportName}

The URL for the integrations API environment:

https://integrations.revoretail.works/api/external/v3/reports/{reportName}

And you should provide the mandatory headers for the authentication

Header Value
username {account-username}
Authorization Bearer {token}
Content-Type application/json

The token is obtained at Account managment section of RevoRetail.

Field Type Required Description
start_date YYYY-mm-dd optional The initial date for the report. Default: start of month.
end_date YYYY-mm-dd optional The final date for the report. Default: today.
page number optional As the data is paginated, use this parameter to select the page to fetch.
pagination number optional Number of objects per page. The default value is 50 and the max allowed is 200.

Response format

{
    "current_page": 1,
    "data": [  [Report data will go here]  ],
    "first_page_url": "https://revoretail.works/api/external/v3/reports/{reportName}?page=1",
    "from": 1,
    "last_page": 4,
    "last_page_url": "https://revoretail.works/api/external/v3/reports/{reportName}?page=4",
    "next_page_url": "https://revoretail.works/api/external/v3/reports/{reportName}?page=2",
    "path": "https://revoretail.works/api/external/v3/reports/{reportName}",
    "per_page": 50,
    "prev_page_url": null,
    "to": 50,
    "total": 190
}

All requests will return the info following this template.
This format is paginated to avoid the system collapse. The object data contains the lines of the requested report.

Available reports

Here you have a list of the available filters.

Available filters

A parte de start_date y end_date se puede filtrar por otros campos (siempre que tengan sentido con el informe). A continuación les dejamos una lista:

Filter  Value Description
start_date YYYY-MM-DD Required The start date of the report
end_date YYYY-MM-DD Required The end date of the report
start_time HH:mm The start time of the report
end_time HH:mm The end time of the report
employee int Id of the employee to filter with
room int Id of the room to filter with
dayofweek int Where Sunday is 1 and Saturday is 7
priceRate int The id of the price rate
cashier int The id of the cashier
discount int The id of the cashier
dateField string The date field that'll be used on filters query (created_at, updated_at, closed_at, opened_at)

Retail (Deprecated)

Authentication

curl --header "username: {account-username}" \
     --header "Authorization: Bearer {token}" \
     --header "Content-Type: application/json" \
     https://revoretail.works/api/external/reports

The main URL for the external API:

https://revoretail.works/api/external/reports

The URL for the integrations API environment:

https://integrations.revoretail.works/api/external/reports

And you should provide the mandatory headers for the authentication

Header Value
username {account-username}
Authorization Bearer {token}
Content-Type application/json

The token is obtained at Account managment section of RevoRetail.

Response format

{
    "current_page": 1,
    "data": [  [Report data will go here]  ],
    "first_page_url": "https://revoretail.works/api/external/reports/{reportName}?page=1",
    "from": 1,
    "last_page": 4,
    "last_page_url": "https://revoretail.works/api/external/reports/{reportName}?page=4",
    "next_page_url": "https://revoretail.works/api/external/reports/{reportName}?page=2",
    "path": "https://revoretail.works/api/external/reports/{reportName}",
    "per_page": 50,
    "prev_page_url": null,
    "to": 50,
    "total": 190
}

All requests will return the info following this template.
This format is paginated to avoid the system collapse. The object data contains the lines of the requested report.

Available reports (Deprecated)

Here you have a list of the available filters.

Available filters (Deprecated)

A parte de start_date y end_date se puede filtrar por otros campos (siempre que tengan sentido con el informe). A continuación les dejamos una lista:

Filter  Value Description
start_date YYYY-MM-DD Required The start date of the report
end_date YYYY-MM-DD Required The end date of the report
start_time HH:mm The start time of the report
end_time HH:mm The end time of the report
employee int Id of the employee to filter with
room int Id of the room to filter with
dayofweek int Where Sunday is 1 and Saturday is 7
priceRate int The id of the price rate
cashier int The id of the cashier
discount int The id of the cashier
dateField string The date field that'll be used on filters query (created_at, updated_at, closed_at, opened_at)