{
  "openapi": "3.1.0",
  "info": {
    "title": "Layout Partner API",
    "version": "1.0.0",
    "summary": "Place real pickup orders at essentially any restaurant.",
    "description": "Provision a user, build a cart, and hand off a link where that person adds a card and confirms. Layout drives the restaurant's own ordering site, so there is no merchant integration and no menu to sync.\n\nSecrets are environment-scoped by prefix: `sk_test_` reaches sandbox, `sk_live_` reaches production. Sandbox runs the real engine against real restaurant sites and never places or charges. Production access is reviewed by hand; until it is granted, a live key is refused with 403.\n\nEvery refusal returns the same body whatever the reason. The real reason appears on the application's Refusals page in the console, never in the response.",
    "contact": {
      "name": "Layout",
      "url": "https://developer.layout.link"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://layout.link/legal/terms-conditions"
    }
  },
  "servers": [
    {
      "url": "https://api.layout.link/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Users",
      "description": "People you can order for."
    },
    {
      "name": "Orders",
      "description": "Building carts and reading outcomes."
    },
    {
      "name": "Events",
      "description": "Poll parity with webhooks."
    }
  ],
  "paths": {
    "/users": {
      "post": {
        "tags": [
          "Users"
        ],
        "operationId": "createUser",
        "summary": "Provision a user",
        "description": "Idempotent on phone number: calling twice returns the same user rather than a duplicate. Mints nothing that can spend.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "firstName",
                  "lastName",
                  "phone"
                ],
                "properties": {
                  "firstName": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "Shown to the person on the consent screen."
                  },
                  "lastName": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "Shown to the person on the consent screen."
                  },
                  "phone": {
                    "type": "string",
                    "pattern": "^\\+[1-9]\\d{6,14}$",
                    "description": "E.164. The person verifies it themselves on the handoff link.",
                    "example": "+19165550142"
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254,
                    "description": "Optional and unverified. Kept with the provisioning link; never written to the person's own Layout account, never shown to another partner.",
                    "example": "dana@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created, or the existing user for this phone number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Secret not recognised, malformed, or rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Application not permitted. Usually production on an unapproved application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-minute or per-day ceiling reached.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait. Absent on a daily ceiling, which resets at UTC midnight."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/users/{userId}/grant": {
      "post": {
        "tags": [
          "Users"
        ],
        "operationId": "createGrant",
        "summary": "Mint a build-only grant token",
        "description": "For handing an MCP session the wheel for one person. The token builds carts and reads state; it cannot add a card, confirm, or spend.",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "usr_4b8e"
          }
        ],
        "responses": {
          "201": {
            "description": "Token minted. Returned exactly once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grant"
                }
              }
            }
          },
          "401": {
            "description": "Secret not recognised, malformed, or rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Application not permitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such user for this application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "That person has claimed their own Layout account. Stop ordering on their behalf.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "already_claimed",
                    "message": "That account has been claimed."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/users/{userId}/connect": {
      "post": {
        "tags": [
          "Users"
        ],
        "operationId": "connectUser",
        "summary": "Get a consent link for an existing Layout account",
        "description": "For someone who already has a Layout account (provisioning returns next: 'connect'). Returns a link to the Layout consent screen; send its owner there to connect their account to your app. Nothing is attached until they approve, signed in, holding the number you provisioned. Returns { connected: true } if you are already connected.",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "usr_4b8e"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "returnUrl": {
                    "type": "string",
                    "format": "uri",
                    "description": "Optional. https only. Where to send the person after they decide.",
                    "example": "https://your.app/connected"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A consent link, or already connected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connectUrl": {
                      "type": "string",
                      "format": "uri",
                      "example": "https://account.layout.link/authorize?request_id=…"
                    },
                    "expiresInSec": {
                      "type": "integer",
                      "example": 600
                    },
                    "connected": {
                      "type": "boolean",
                      "description": "True when you are already connected to this person; no link is issued.",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Secret not recognised, malformed, or rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Application not permitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such user for this application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A per-minute ceiling was reached.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/users/{userId}/verify/start": {
      "post": {
        "tags": [
          "Users"
        ],
        "operationId": "startVerification",
        "summary": "Text the person a verification code",
        "description": "Sends a six-digit code to the number you provisioned the user with, good for three minutes. The person never types their number; they read the code back to you. Idempotent per your ceilings; a code requested too soon for the same number is refused.",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "usr_4b8e"
          }
        ],
        "responses": {
          "200": {
            "description": "Code sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "id": {
                      "type": "string",
                      "example": "usr_4b8e"
                    },
                    "phoneHint": {
                      "type": "string",
                      "description": "The masked number the code went to — a confirmation, never the full value.",
                      "example": "+1 (916) •••-0142"
                    },
                    "expiresInSec": {
                      "type": "integer",
                      "example": 180
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Secret not recognised, malformed, or rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Application not permitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such user for this application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "This person has already verified and claimed their account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "already_claimed",
                    "message": "This person has already verified their phone and claimed their Layout account."
                  }
                }
              }
            }
          },
          "429": {
            "description": "A verification ceiling was reached.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The code could not be sent. Nothing was spent; retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/users/{userId}/verify": {
      "post": {
        "tags": [
          "Users"
        ],
        "operationId": "verifyCode",
        "summary": "Submit the code the person read back",
        "description": "Verifies the phone. A good code marks it verified and, for a new Layout account, claims the placeholder onto it. Returns no session. Calling it again after a successful verify returns verified: true without a fresh code.",
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "usr_4b8e"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "pattern": "^\\d{6}$",
                    "description": "The six-digit code the person read back.",
                    "example": "246810"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean",
                      "const": true
                    },
                    "status": {
                      "type": "string",
                      "description": "The link's status after verification.",
                      "enum": [
                        "active",
                        "provisioned"
                      ],
                      "example": "active"
                    },
                    "claimed": {
                      "type": "boolean",
                      "description": "True when a new Layout account was claimed onto this person. False when they already had their own account, which they keep.",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A six-digit code is required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The code is not valid. Ask the person for the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "bad_code",
                    "message": "That code is not valid. Ask the person for the current code."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Application not permitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such user for this application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts for this number.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders": {
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "createOrder",
        "summary": "Removed — ordering runs over MCP",
        "description": "Retired. This endpoint answers 410 for every method. Ordering runs over Layout's MCP interface: mint a build session with POST /users/{userId}/grant, connect the lgb_ token to https://mcp.layout.link, and drive the order tool (preview, then build). REST provisions users, reads orders, and carries webhooks.",
        "responses": {
          "410": {
            "description": "Gone. Build over MCP instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders/{orderId}": {
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "getOrder",
        "summary": "Read an order",
        "description": "An order belonging to another application is a 404, never a 403.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "ord_7c21"
          }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "description": "Secret not recognised, malformed, or rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Application not permitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such order for this application.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Events"
        ],
        "operationId": "listEvents",
        "summary": "Poll the event feed",
        "description": "The same events your webhooks receive. Useful when your endpoint was down, or instead of running one.",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "nextCursor from a previous page."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Null when you have reached the end."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Secret not recognised, malformed, or rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Application not permitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "tags": [
          "Users"
        ],
        "operationId": "whoami",
        "summary": "Readiness check",
        "description": "What the key you called with resolves to. environment is set by the key prefix; approvalStatus is the application's real status, so a sandbox key returns pending for an unapproved app. Check it before minting a live key: production is refused until approval.",
        "responses": {
          "200": {
            "description": "Who you are.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "app": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "example": "app_1a2b"
                        }
                      }
                    },
                    "environment": {
                      "type": "string",
                      "enum": [
                        "sandbox",
                        "live"
                      ]
                    },
                    "approvalStatus": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "approved",
                        "denied",
                        "suspended"
                      ]
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "example": [
                        "order:preview",
                        "order:build",
                        "order:status",
                        "order_status",
                        "places"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Secret not recognised, malformed, or rotated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Invalid API credentials."
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your client secret. `sk_test_` for sandbox, `sk_live_` for production. Read from the Authorization header and nowhere else."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "example": "rate_limited"
              },
              "message": {
                "type": "string"
              },
              "orderId": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "usr_4b8e"
          },
          "status": {
            "type": "string",
            "example": "provisioned"
          },
          "next": {
            "type": "string",
            "enum": [
              "verify",
              "connect",
              "awaiting_user_approval",
              "connected"
            ],
            "description": "The next step for this person. 'verify': a new number, prove it headlessly. 'connect': this number already has a Layout account and you passed connectDelivery:'developer' — send its owner to connect.connectUrl. 'awaiting_user_approval': an existing account, and Layout has texted the person the approval link itself (the default) — see awaitingApproval. 'connected': already connected, start building."
          },
          "connect": {
            "type": "object",
            "description": "Present only when next is 'connect' (you asked to deliver the link yourself).",
            "properties": {
              "connectUrl": {
                "type": "string",
                "format": "uri",
                "example": "https://account.layout.link/authorize?request_id=…",
                "description": "Open in the person's browser. They approve on the Layout consent screen."
              },
              "expiresInSec": {
                "type": "integer",
                "example": 600
              }
            }
          },
          "awaitingApproval": {
            "type": "object",
            "description": "Present only when next is 'awaiting_user_approval'. Layout texted the person; wait on them.",
            "properties": {
              "phoneHint": {
                "type": "string",
                "example": "(•••) •••-0142",
                "description": "Masked confirmation of the number Layout texted."
              },
              "expiresInSec": {
                "type": "integer",
                "example": 600
              }
            }
          },
          "session": {
            "type": "object",
            "description": "Build-only. Cannot add a card, confirm, or spend.",
            "properties": {
              "scope": {
                "type": "string",
                "const": "build"
              },
              "expiresIn": {
                "type": "integer",
                "example": 3600
              }
            }
          },
          "handoffUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://account.layout.link/join",
            "description": "Where the person claims the account and proves their phone."
          }
        }
      },
      "Grant": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "example": "lgb_9f3a2c…",
            "description": "Returned exactly once. Build-only."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "scope": {
            "type": "string",
            "const": "build"
          }
        }
      },
      "OrderAccepted": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null until there is an order to point at."
          },
          "state": {
            "type": "string",
            "const": "building"
          },
          "idempotencyKey": {
            "type": "string"
          }
        }
      },
      "Order": {
        "type": "object",
        "description": "Field names match the webhook payload (snake_case), not camelCase.",
        "properties": {
          "id": {
            "type": "string",
            "example": "ord_7c21"
          },
          "state": {
            "$ref": "#/components/schemas/OrderState"
          },
          "store": {
            "type": [
              "string",
              "null"
            ],
            "example": "Sweetgreen, Market St"
          },
          "items": {
            "type": "integer",
            "example": 2
          },
          "total_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units. 2140 is $21.40.",
            "example": 2140
          },
          "currency": {
            "type": "string",
            "example": "usd"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "placed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failure_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "A stable machine code on a failed order. Never prose."
          }
        }
      },
      "OrderState": {
        "type": "string",
        "enum": [
          "building",
          "carted",
          "placing",
          "placed",
          "failed",
          "unconfirmed",
          "refunded"
        ],
        "description": "`placed` is the only state that means the order exists. `unconfirmed` means Layout cannot verify either way: do not retry it and do not show it as failed, because collapsing the two is what causes duplicate orders and double charges."
      },
      "Event": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "order.building",
              "order.carted",
              "order.placing",
              "order.placed",
              "order.failed",
              "order.unconfirmed",
              "order.needs_approval",
              "order.challenge"
            ]
          },
          "delivery_id": {
            "type": "string"
          },
          "test": {
            "type": "boolean",
            "description": "Present and true on a delivery sent by the console's Send test event button. Ignore these in production handlers."
          },
          "order": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "state": {
                "$ref": "#/components/schemas/OrderState"
              },
              "store": {
                "type": "string"
              },
              "items": {
                "type": "integer"
              },
              "total_minor": {
                "type": "integer"
              },
              "currency": {
                "type": "string"
              }
            }
          },
          "user": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              }
            }
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}