Connect
The API lives behind one GraphQL endpoint. We hand you three values at onboarding — keep the key secret and out of client-side code.
| Value | Shape | What it's for |
|---|---|---|
| Endpoint URL | https://<id>.appsync-api.<region>.amazonaws.com/graphql | Where every request is POSTed. |
| API key | da2-… (AWS AppSync format) | Opens the public endpoints (login, ping). Sent as x-api-key. |
| Region | us-east-2 | The AWS region the endpoint runs in. |
| serverUrl | https://dev.odoo-smartbamboo.mx (test) | Passed to login — identifies the backing ERP for your environment. |
| database | smart-bamboo-dev (test) | Passed to login alongside serverUrl. |
serverUrl and database above. Do not point a test integration at the production values: the endpoint will accept them and you will be reading (and writing) live data. You get the production URL, key, and login parameters once integration is signed off.Authenticate
Authentication is two-tier. The API key gets you to the public endpoints; logging in returns a short-lived JWT that unlocks everything else. Pick the header that matches what you're calling.
JWT token
Sent on every protected query and mutation. Obtained from login.
API key
For the open endpoints — login, ping, isTokenValid.
AWS IAM
SigV4-signed, reserved for our own service-to-service webhooks.
Step 1 — Exchange credentials for a token
Call login with the API key in the header. It returns a JWT valid for roughly 24 hours, plus the signed-in user and company.
mutation Login { login( serverUrl: "https://dev.odoo-smartbamboo.mx" # test env database: "smart-bamboo-dev" # test env username: "partner@example.com" password: "••••••••" ) { token # put this in authorizationToken user { id name email company { id name } } } }
Step 2 — Send the token on protected calls
From here on, set authorizationToken to the JWT. The API key is no longer needed for these requests.
Step 3 — Refresh before it expires
Call refreshToken with a still-valid token to get a fresh one — no need to re-enter credentials.
mutation Refresh { refreshToken { token } }
Make a request
Every call — query or mutation — is a single POST with a JSON body containing query and optional variables. Here's a complete authenticated request over curl.
curl -X POST "$ENDPOINT" \ -H "authorizationToken: $JWT" \ -H "Content-Type: application/json" \ -d '{ "query": "query Products($pl: ID!) { getProducts(pricelistId: $pl) { product { id name basePrice } pricedVariants { variantId price } } }", "variables": { "pl": "<your pricelistId>" } }'
A successful response wraps results under data, keyed by the operation name:
{
"data": {
"getProducts": [
{
"product": { "id": "5659", "name": "Almohada Memory", "basePrice": 499.00 },
"pricedVariants": [ { "variantId": "7210", "price": 449.00 } ]
}
]
}
}Explore live
Run real queries against the endpoint right here. Open the Headers tab in the explorer and add x-api-key (for public operations) or authorizationToken (for everything else), then hit run. The full schema loads by introspection once your headers are accepted.
curl example above instead.Powered by Apollo's embedded Explorer. Nothing you type here is stored on this page.
Schema
The API exposes queries for reading and mutations for writing — roughly 98 of each. Below are the operations most external integrations use, with their exact signatures. The full schema is browsable by introspection in the explorer above (works with x-api-key alone), and the SDL is available on request.
Queries — read data
product + pricedVariants.lastModified per pricelist — poll it to detect price changes.User shape (no phone/vat).posReference.Mutations — write data
token + user).Partner (phone, vat, address).createCart, addItemToCart, …) and terminal payments exist too, but they're tuned for the live POS session. Talk to us before driving the checkout flow externally so we can scope the right operations.Core types
The shapes you'll read most often, exactly as the schema defines them — request only what you need; GraphQL returns exactly the fields you select.
type ProductForSale { # what getProducts returns product: Product! pricedVariants: [PricedVariant!]! # price per variant on the requested pricelist } type Product { id: ID! name: String! imageUrl: String basePrice: Float! productType: String category: ProductCategory! posCategory: ProductCategory attributeOptions: [ProductAttribute!]! variants: [ProductVariant!]! } type ProductVariant { id: ID! # the sellable unit — use this id everywhere basePrice: Float! imageUrl: String selectedAttributes: [SelectedAttribute!]! } type PricedVariant { variantId: ID!, price: Float! } type Pricelist { id: ID!, name: String! } type PricelistVersion { pricelistId: ID!, lastModified: String }
defaultCode) and barcode are not exposed on the catalog types yet. Map products by ProductVariant.id for now — it's stable and unique per sellable unit. Ask us if your integration needs SKU/barcode; it's on the roadmap.type User { # what searchPartners returns id: ID! name: String! username: String! email: String! company: CompanyInfo! # { id, name } } type Partner { # returned by createPartner / updatePartner id: ID! name: String! email: String phone: String mobile: String vat: String # tax id (RFC) street: String city: String zip: String }
type Order { # what searchOrders returns id: ID! name: String! posReference: String # receipt reference (not "reference") customerId: ID customerName: String dateOrder: String! state: OrderState! # enum amountTotal: Float! amountTax: Float! amountPaid: Float! lines: [OrderLine!]! payments: [OrderPayment!]! } type OrderLine { id: ID! productId: ID! productName: String! variantId: ID quantity: Float! unitPrice: Float! discount: Float taxAmount: Float! priceSubtotal: Float! # before tax priceTotal: Float! # tax-inclusive } type PosOrderDetail { # what getOrderById returns id: ID! name: String! posReference: String receiptNumber: String customerId: ID customerName: String dateOrder: String datePaid: String state: String amountSubtotal: Float amountTax: Float amountTotal: Float amountPaid: Float lines: [PosOrderLine!] # id, productId, productName, quantity, payments: [PosOrderPayment!] # unitPrice, discount, priceSubtotal, priceTotal }
Field names follow GraphQL camelCase. Ids are GraphQL ID (strings on the wire) that carry the ERP integer ids. Money is decimal Float; priceSubtotal is pre-tax and priceTotal is tax-inclusive.
Errors
The HTTP status is almost always 200 — GraphQL reports problems in an errors array alongside (or instead of) data. Always check for errors before trusting data.
{
"errors": [
{ "message": "Not Authorized to access getProducts" }
]
}| errorType / message | Cause | Fix |
|---|---|---|
Not Authorized | Missing, expired, or wrong-tier token. | Re-run login or refresh; confirm you're using authorizationToken. |
UnauthorizedException | Bad or absent x-api-key on a public call. | Check the API key header and value. |
MissingFieldArgument | A required argument was omitted (e.g. pricelistId on getProducts). | Check the signature in the schema section or the explorer. |
UnknownArgument | An argument that doesn't exist in the schema. | Introspect the field; argument names are exact. |
FieldUndefined | Selecting a field the type doesn't have. | Check the type in the explorer's schema tab. |
Could not reach the authentication server | login couldn't authenticate against the serverUrl/database pair — wrong values or wrong credentials for that database. | Confirm you're using the environment's exact serverUrl and database (see Connect) and that your user was provisioned in that database. |
REST endpoints legacy
A small REST surface exists for backward compatibility. Prefer GraphQL for new work — these routes will not gain features. Base URL: https://<id>.execute-api.<region>.amazonaws.com/<env>.
| Method | Path | Purpose |
|---|---|---|
POST | /auth/login | Authenticate (use GraphQL login instead). |
GET | /partners | Search customers. |
POST | /partners | Create a customer. |
PUT | /partners/{id} | Update a customer. |
GET | /products | Search products. |
GET | /pricelists | List pricelists. |