{"openapi":"3.1.0","info":{"title":"Nookivia API","description":"# Introduction\n\nThe Nookivia API lets your own scripts, and apps you have approved, read and change\nthe trips on your Nookivia account.\n\nIt speaks JSON over HTTPS. Send requests to the server named at the top of this\nreference. Every trip route needs a credential; the pages in this section cover what\nall of them share, and the routes follow.\n\nTo get started, read Authentication, then try `GET /api/v1/trips`.\n\n# Authentication\n\nEvery request acts as one **authenticated user**: the person who created the API key,\nor who approved the app holding the OAuth token. It can only ever see and change that\nuser's trips.\n\nSend the credential on every request:\n\n```http\nAuthorization: Bearer <credential>\n```\n\n## API keys\n\nFor your own scripts. An API key (`ak_...`) acts as the user who created it, and\nholds only the scopes it was created with. Keep it secret, like a password: anyone\nholding it can do whatever its scopes allow.\n\nAPI keys are not available to create yet. [The developers page](/developers/) will\ngive the steps when they are.\n\n## OAuth\n\nFor apps that act for other people. An app sends the user to Nookivia to approve it,\nthen exchanges the result for an access token. It gets only the scopes the user\napproved.\n\nNookivia uses the OAuth 2.0 authorization code flow with PKCE. Apps are public clients\nand hold no client secret. The authorization and token URLs are in this document's\n`oauthToken` security scheme.\n\n## Scopes\n\n| Scope | Allows |\n| --- | --- |\n| `trips:read` | See your trips. |\n| `trips:write` | Create, change and delete your trips. |\n\nA route needing a scope the credential does not hold answers 403\n`insufficient_scope`, with a challenge naming the missing scope:\n\n```http\nWWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"trips:write\"\n```\n\nA missing, invalid, expired or revoked credential answers 401 `unauthorized`.\n\n# Rate limits\n\nRequests are limited per minute, in two ways:\n\n| Limit | Requests per minute |\n| --- | --- |\n| Per client address, before the credential is checked | 300 |\n| Per credential | 120 |\n\nOver either limit, a request answers 429 `rate_limited` with a `Retry-After` header\ngiving the seconds to wait. Wait at least that long before trying again.\n\nThe limits are counted close to where your request enters the network, so treat them\nas a ceiling rather than an exact quota, and always honour `Retry-After`. The\nnumbers may change.\n\n# Errors\n\nEvery failure has the same JSON body:\n\n```json\n{\n  \"error\": {\n    \"code\": \"invalid_if_match\",\n    \"message\": \"If-Match: * is not accepted -- send the version you last read\"\n  }\n}\n```\n\nBranch on `code`, not on `message`: `message` is written for a person, names the\nfield it is about, and its wording may change. A 409 `version_conflict` also carries\n`trip`, the trip as it now stands.\n\n| Status | `code` | Meaning |\n| --- | --- | --- |\n| 400 | `invalid_body` | The body is not JSON, or not a JSON object. |\n| 400 | `invalid_if_match` | `If-Match` is not a version this API issued. `*` is refused too. |\n| 400 | `invalid_query` | A query parameter is out of range, unknown, or a `cursor` this API did not issue. |\n| 400 | `unknown_field` | The body has a key a trip does not have. Nothing was saved. |\n| 401 | `unauthorized` | No credential, a credential that is invalid, expired or revoked, or one for a user who no longer has an active Nookivia account. |\n| 403 | `cross_site_request` | A browser sent Nookivia's own sign-in cookie on a change from another website. Send a bearer credential instead. |\n| 403 | `forbidden` | The credential does not act for a user. |\n| 403 | `insufficient_scope` | The credential was not granted the scope this route needs. `WWW-Authenticate` names it. |\n| 404 | `not_found` | No trip with that id on the authenticated user's account. |\n| 409 | `slug_conflict` | Another trip on the account already uses that slug. Choose another, or leave it out. |\n| 409 | `version_conflict` | The trip changed since you read it. The body carries the trip as it now stands, with its new `ETag`. |\n| 413 | `payload_too_large` | The body is larger than 16384 bytes. |\n| 415 | `unsupported_media_type` | The body is not declared as JSON. Send `Content-Type: application/json`. |\n| 422 | `validation_failed` | A field broke one of its rules. `message` says which rule, and names the field. |\n| 428 | `precondition_required` | A change or delete was sent without `If-Match`. |\n| 429 | `rate_limited` | Too many requests. Wait the number of seconds in `Retry-After`. |\n\nNew codes may be added within v1. Treat a code you do not recognise by its HTTP status.\n\n# Pagination\n\n`GET /api/v1/trips` returns one page at a time:\n\n```json\n{ \"trips\": [ … ], \"nextCursor\": \"…\" }\n```\n\n- `limit` sets the page size, from 1 to 100. The default is 50.\n- Pass `nextCursor` back as `cursor` to get the next page. It is `null` on the last.\n- The response also carries the next page as a `Link: <…>; rel=\"next\"` header.\n\nA cursor is opaque: pass it back unchanged, and do not build or read one. A trip\ncreated or deleted while you page never repeats and never goes missing.\n\n# Changing a trip\n\nEvery trip carries a `version`, which goes up by one on every change. Responses repeat\nit as a weak `ETag`:\n\n```http\nETag: W/\"3\"\n```\n\nTo replace (`PUT`), change (`PATCH`) or delete a trip, send that value back as\n`If-Match`:\n\n```http\nIf-Match: W/\"3\"\n```\n\n- If the trip has changed since, the request answers 409 `version_conflict` with the\n  trip as it now stands. Show the user what changed, then retry with the new `ETag`.\n- Without `If-Match` it answers 428 `precondition_required`.\n- `If-Match: *` is refused with 400: a change always names the version it expects,\n  so no edit is ever silently overwritten.\n\n`PUT` replaces every settable field; `PATCH` changes only the fields you send.\n\n# Requests and formats\n\n- **Bodies** are JSON, sent with `Content-Type: application/json`, and at most\n  16384 bytes. A key a trip does not have is refused rather than ignored.\n- **Dates** are whole days, `2027-03-02`, with no time and no zone.\n- **Timestamps** (`createdAt`, `updatedAt`) are ISO 8601 in UTC with an explicit\n  offset, `2027-03-02T09:30:00+00:00` — never `Z`.\n- **Ids** are assigned by the server. Address a trip by `id`; its `slug` is for URLs.\n- **Browsers** can call the API from any website with a bearer credential: responses\n  allow every origin, and expose `ETag`, `Location`, `Retry-After` and\n  `WWW-Authenticate`.\n\n# Versioning\n\nThe major version is in the path: `/api/v1/...`.\n\nWithin v1, changes are additive only — new optional fields, new routes, new error\ncodes — so a client should ignore fields and codes it does not know. A breaking change\nships as `/api/v2/...` alongside v1, which then carries `Deprecation` and `Sunset`\nheaders until it is withdrawn.","version":"1.1.0"},"tags":[{"name":"Trips","description":"The authenticated user's trips."}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key (`ak_...`), sent as `Authorization: Bearer <key>`. It acts as the user who created it, and holds only the scopes it was created with."},"oauthToken":{"type":"oauth2","description":"OAuth 2.0, authorization code flow with PKCE: apps are public clients and hold no secret. Send the access token as `Authorization: Bearer <token>`. It holds only the scopes the user granted.","flows":{"authorizationCode":{"authorizationUrl":"https://clerk.app.nookivia.com/oauth/authorize","tokenUrl":"https://clerk.app.nookivia.com/oauth/token","scopes":{"trips:read":"See your trips.","trips:write":"Create, change and delete your trips."},"x-usePkce":"SHA-256"}}}},"schemas":{"TripList":{"type":"object","required":["trips","nextCursor"],"properties":{"trips":{"type":"array","items":{"$ref":"#/components/schemas/Trip"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` to get the next page; `null` on the last. Opaque -- do not build or parse one."}}},"Trip":{"type":"object","description":"A trip on the authenticated user's account.","required":["id","slug","version","title","status","startDate","endDate","summary","travellers","countries","createdAt","updatedAt"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9-]+$","description":"The trip's API identity: a lowercase ULID assigned by the server."},"slug":{"type":"string","pattern":"^[a-z0-9-]+$","description":"A readable URL segment, unique on the account. Address trips in the API by `id`, not by slug. When a write leaves it out, the server derives one from the title and start date."},"version":{"type":"integer","minimum":1,"description":"Goes up by one on every change. The `ETag` header carries it; send that back as `If-Match` to change the trip."},"title":{"type":"string","minLength":1,"maxLength":200},"status":{"type":"string","enum":["booked","planning"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"A whole day, `2027-03-02`. Not an instant: it has no time and no zone."},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"A whole day, `2027-03-02`. Not an instant: it has no time and no zone."},"summary":{"type":["string","null"],"maxLength":500},"travellers":{"type":"integer","minimum":1,"description":"How many people are on the trip. At least one."},"countries":{"type":"array","items":{"type":"string","pattern":"^[A-Z]{2}$","description":"A two-letter uppercase ISO country code."}},"createdAt":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,9})?\\+00:00$","description":"An instant in UTC: ISO 8601 ending `+00:00`, never `Z` and never bare."},"updatedAt":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,9})?\\+00:00$","description":"An instant in UTC: ISO 8601 ending `+00:00`, never `Z` and never bare."}}},"ApiError":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","not_found","invalid_body","unknown_field","validation_failed","slug_conflict","version_conflict","precondition_required","invalid_if_match","forbidden","insufficient_scope","cross_site_request","payload_too_large","unsupported_media_type","rate_limited","invalid_query"]},"message":{"type":"string","description":"Written for a person, and names the field it is about. Branch on `code`, not on this: its wording may change."}}}}},"TripInput":{"type":"object","required":["title","status","startDate","endDate"],"additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"status":{"type":"string","enum":["booked","planning"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"A whole day, `2027-03-02`. Not an instant: it has no time and no zone."},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"A whole day, `2027-03-02`. Not an instant: it has no time and no zone."},"summary":{"type":["string","null"],"maxLength":500},"travellers":{"type":"integer","minimum":1,"description":"How many people are on the trip. At least one."},"countries":{"type":"array","minItems":1,"items":{"type":"string","pattern":"^[A-Z]{2}$","description":"A two-letter uppercase ISO country code."},"description":"Every country the trip visits, in the order the itinerary meets them."},"slug":{"type":"string","pattern":"^[a-z0-9-]+$","description":"A readable URL segment, unique on the account. Address trips in the API by `id`, not by slug. When a write leaves it out, the server derives one from the title and start date."}}},"WriteConflict":{"type":"object","required":["error"],"description":"`trip` is present when `error.code` is `version_conflict`, carrying the trip as it now stands, and absent when it is `slug_conflict`.","properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","not_found","invalid_body","unknown_field","validation_failed","slug_conflict","version_conflict","precondition_required","invalid_if_match","forbidden","insufficient_scope","cross_site_request","payload_too_large","unsupported_media_type","rate_limited","invalid_query"]},"message":{"type":"string","description":"Written for a person, and names the field it is about. Branch on `code`, not on this: its wording may change."}}},"trip":{"$ref":"#/components/schemas/Trip"}}},"VersionConflict":{"type":"object","required":["error","trip"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","not_found","invalid_body","unknown_field","validation_failed","slug_conflict","version_conflict","precondition_required","invalid_if_match","forbidden","insufficient_scope","cross_site_request","payload_too_large","unsupported_media_type","rate_limited","invalid_query"]},"message":{"type":"string","description":"Written for a person, and names the field it is about. Branch on `code`, not on this: its wording may change."}}},"trip":{"$ref":"#/components/schemas/Trip"}}}}},"paths":{"/api/v1/trips":{"get":{"operationId":"getApiV1Trips","tags":["Trips"],"summary":"List your trips","description":"The authenticated user’s trips in `startDate` order, then by `id`, one page at a time. Follow `nextCursor` -- or the `Link: rel=\"next\"` header -- until it is `null`. A trip created or deleted while you page never repeats and never goes missing.","security":[{"apiKey":["trips:read"]},{"oauthToken":["trips:read"]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Trips per page, from 1 to 100. Defaults to 50.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` from the previous page. Opaque: pass it back unchanged, and do not build or read one -- its encoding may change.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of the authenticated user's trips.","headers":{"Link":{"description":"The next page, as `<...>; rel=\"next\"` (RFC 8288). Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripList"}}}},"400":{"description":"A `limit` out of range or not a whole number, a `cursor` this API did not issue, or a query parameter the list does not take (`invalid_query`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No valid credential, or a credential for a user who no longer has an active Nookivia account. Every trip route requires one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but may not do this: it was not granted the scope the route needs (`insufficient_scope`, with an RFC 6750 `WWW-Authenticate` challenge naming it), or it does not act for a user (`forbidden`).","headers":{"WWW-Authenticate":{"description":"On `insufficient_scope`: `Bearer error=\"insufficient_scope\", scope=\"…\"`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests, from this address or on this credential. Limits are per minute; wait the number of seconds in `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"post":{"operationId":"postApiV1Trips","tags":["Trips"],"summary":"Create a trip","description":"The server assigns `id`, `version` (1) and, unless you send one, `slug` -- derived from the title and start date.","security":[{"apiKey":["trips:write"]},{"oauthToken":["trips:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripInput"}}}},"responses":{"201":{"description":"Created.","headers":{"ETag":{"description":"The trip's version, weak-validated. Send it as `If-Match` to write.","schema":{"type":"string","pattern":"^W/\"\\d+\"$"}},"Location":{"description":"The new trip.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trip"}}}},"400":{"description":"Not JSON, not an object, or a key a trip does not have (`unknown_field`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No valid credential, or a credential for a user who no longer has an active Nookivia account. Every trip route requires one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but may not do this: it was not granted the scope the route needs (`insufficient_scope`, with an RFC 6750 `WWW-Authenticate` challenge naming it), or it does not act for a user (`forbidden`).","headers":{"WWW-Authenticate":{"description":"On `insufficient_scope`: `Bearer error=\"insufficient_scope\", scope=\"…\"`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The slug you chose is already used by another trip on the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"The body is larger than 16384 bytes (`payload_too_large`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"The body is not declared as JSON (`unsupported_media_type`). Send `Content-Type: application/json`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"A field broke one of its rules. `message` says which rule, and names the field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests, from this address or on this credential. Limits are per minute; wait the number of seconds in `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/v1/trips/{id}":{"get":{"operationId":"getApiV1TripsById","tags":["Trips"],"summary":"Read a trip","security":[{"apiKey":["trips:read"]},{"oauthToken":["trips:read"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The trip id. A value that could not be a trip id answers 404.","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}}],"responses":{"200":{"description":"The trip.","headers":{"ETag":{"description":"The trip's version, weak-validated. Send it as `If-Match` to write.","schema":{"type":"string","pattern":"^W/\"\\d+\"$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trip"}}}},"401":{"description":"No valid credential, or a credential for a user who no longer has an active Nookivia account. Every trip route requires one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but may not do this: it was not granted the scope the route needs (`insufficient_scope`, with an RFC 6750 `WWW-Authenticate` challenge naming it), or it does not act for a user (`forbidden`).","headers":{"WWW-Authenticate":{"description":"On `insufficient_scope`: `Bearer error=\"insufficient_scope\", scope=\"…\"`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No trip with that id on the authenticated user's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests, from this address or on this credential. Limits are per minute; wait the number of seconds in `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"put":{"operationId":"putApiV1TripsById","tags":["Trips"],"summary":"Replace a trip","description":"Replaces every settable field. Send the trip’s `ETag` as `If-Match`: if the trip has changed since you read it, the write answers 409 rather than overwriting.","security":[{"apiKey":["trips:write"]},{"oauthToken":["trips:write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The trip id. A value that could not be a trip id answers 404.","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}},{"name":"If-Match","in":"header","required":true,"description":"The trip's `ETag` from your last read. Required: without it the request answers 428. `*` is refused with 400, because a change always names the version it expects.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripInput"}}}},"responses":{"200":{"description":"The trip.","headers":{"ETag":{"description":"The trip's version, weak-validated. Send it as `If-Match` to write.","schema":{"type":"string","pattern":"^W/\"\\d+\"$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trip"}}}},"400":{"description":"A malformed body (`invalid_body`, `unknown_field`) or an `If-Match` that is not a version, including `*` (`invalid_if_match`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No valid credential, or a credential for a user who no longer has an active Nookivia account. Every trip route requires one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but may not do this: it was not granted the scope the route needs (`insufficient_scope`, with an RFC 6750 `WWW-Authenticate` challenge naming it), or it does not act for a user (`forbidden`).","headers":{"WWW-Authenticate":{"description":"On `insufficient_scope`: `Bearer error=\"insufficient_scope\", scope=\"…\"`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No trip with that id on the authenticated user's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The trip changed since you last read it (`version_conflict`, which carries the trip as it now stands so you can show what changed), or the slug you chose is taken (`slug_conflict`).","headers":{"ETag":{"description":"The trip's version, weak-validated. Send it as `If-Match` to write.","schema":{"type":"string","pattern":"^W/\"\\d+\"$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteConflict"}}}},"413":{"description":"The body is larger than 16384 bytes (`payload_too_large`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"The body is not declared as JSON (`unsupported_media_type`). Send `Content-Type: application/json`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"A field broke one of its rules. `message` says which rule, and names the field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"428":{"description":"No `If-Match`. Send the version you last read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests, from this address or on this credential. Limits are per minute; wait the number of seconds in `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"operationId":"patchApiV1TripsById","tags":["Trips"],"summary":"Change some of a trip","description":"Changes only the fields you send. Send the trip’s `ETag` as `If-Match`: if the trip has changed since you read it, the write answers 409 rather than merging over the other change.","security":[{"apiKey":["trips:write"]},{"oauthToken":["trips:write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The trip id. A value that could not be a trip id answers 404.","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}},{"name":"If-Match","in":"header","required":true,"description":"The trip's `ETag` from your last read. Required: without it the request answers 428. `*` is refused with 400, because a change always names the version it expects.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Any subset of the settable fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripInput"}}}},"responses":{"200":{"description":"The trip.","headers":{"ETag":{"description":"The trip's version, weak-validated. Send it as `If-Match` to write.","schema":{"type":"string","pattern":"^W/\"\\d+\"$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trip"}}}},"400":{"description":"A malformed body (`invalid_body`, `unknown_field`) or an `If-Match` that is not a version, including `*` (`invalid_if_match`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No valid credential, or a credential for a user who no longer has an active Nookivia account. Every trip route requires one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but may not do this: it was not granted the scope the route needs (`insufficient_scope`, with an RFC 6750 `WWW-Authenticate` challenge naming it), or it does not act for a user (`forbidden`).","headers":{"WWW-Authenticate":{"description":"On `insufficient_scope`: `Bearer error=\"insufficient_scope\", scope=\"…\"`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No trip with that id on the authenticated user's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The trip changed since you last read it (`version_conflict`, which carries the trip as it now stands so you can show what changed), or the slug you chose is taken (`slug_conflict`).","headers":{"ETag":{"description":"The trip's version, weak-validated. Send it as `If-Match` to write.","schema":{"type":"string","pattern":"^W/\"\\d+\"$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteConflict"}}}},"413":{"description":"The body is larger than 16384 bytes (`payload_too_large`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"415":{"description":"The body is not declared as JSON (`unsupported_media_type`). Send `Content-Type: application/json`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"A field broke one of its rules. `message` says which rule, and names the field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"428":{"description":"No `If-Match`. Send the version you last read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests, from this address or on this credential. Limits are per minute; wait the number of seconds in `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"operationId":"deleteApiV1TripsById","tags":["Trips"],"summary":"Delete a trip","security":[{"apiKey":["trips:write"]},{"oauthToken":["trips:write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The trip id. A value that could not be a trip id answers 404.","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}},{"name":"If-Match","in":"header","required":true,"description":"The trip's `ETag` from your last read. Required: without it the request answers 428. `*` is refused with 400, because a change always names the version it expects.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"400":{"description":"An `If-Match` that is not a version, including `*`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No valid credential, or a credential for a user who no longer has an active Nookivia account. Every trip route requires one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The credential is valid but may not do this: it was not granted the scope the route needs (`insufficient_scope`, with an RFC 6750 `WWW-Authenticate` challenge naming it), or it does not act for a user (`forbidden`).","headers":{"WWW-Authenticate":{"description":"On `insufficient_scope`: `Bearer error=\"insufficient_scope\", scope=\"…\"`.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No trip with that id on the authenticated user's account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The trip changed since you last read it. The body carries it as it now stands.","headers":{"ETag":{"description":"The trip's version, weak-validated. Send it as `If-Match` to write.","schema":{"type":"string","pattern":"^W/\"\\d+\"$"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VersionConflict"}}}},"428":{"description":"No `If-Match`. Send the version you last read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Too many requests, from this address or on this credential. Limits are per minute; wait the number of seconds in `Retry-After`.","headers":{"Retry-After":{"description":"Seconds to wait before trying again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"servers":[{"url":"https://app.nookivia.com","description":"nookivia-production-01"}]}