Stick to the fundies; they are fundamental for a reason.
<scheme>://<user>:<password>@<server.tld>:<port>/path;parameter?query#fragment
(note that the usage of user:password
to identify userinfo is deprecated and is a generally bad practice. They are included for completeness' sake as they are still technically valid segments of a URI)
Those are your fundamental blocks for building URIs.
You may be opposed to the aesthetics of your URI because you aren't adequately using the tools at your disposal. There is nothing particularly wrong with your URI snippets. But if your data is not hierarchical -I can think of no particular reason why a person and a pet should naturally be hierarchical data in a generic domain- your objection likely stems from the fact that that is the exact relationship you have modeled; a hierarchical one.
Keep in mind here that with the proper understanding, you can craft your URIs to read as prosaically as you wish. So -first- let me import some details from the URI spec.
3.3. Path
The path component contains data, usually organized in hierarchical
form
...
URI producing applications
often use the reserved characters allowed in a segment to delimit
scheme-specific or dereference-handler-specific subcomponents. For
example, the semicolon (";") and equals ("=") reserved characters are
often used to delimit parameters and parameter values applicable to
that segment. The comma (",") reserved character is often used for
similar purposes.
That part about path parameters is usually glossed over and underutilized
3.4. Query
The query component contains non-hierarchical data
The query is very well utilized (over-utilized in my opinion and usually while ignoring that little detail about "non-hierarchical" data)
3.5. Fragment
The fragment identifier component of a URI allows indirect
identification of a secondary resource by reference to a primary
resource and additional identifying information. The identified
secondary resource may be some portion or subset of the primary
resource, some view on representations of the primary resource, or
some other resource defined or described by those representations. A
fragment identifier component is indicated by the presence of a
number sign ("#") character and terminated by the end of the URI.
The fragment is in widespread use, but usually within a narrow scope of identifying specific representations or switching out view fragments.
With that information brought into discussion, what kind of relationship between people and pets are you trying to model with your API? I personally feel it is very clunky and unnatural to use the "traditional" approach of drilling down the hierarchy as you have with /users/1/pets/2
even without considering the identifiers. There is nothing inherently hierarchical in the relationship between users and pets by my understanding. If a hierarchical relationship is what you are going for, then that is exactly what you have modeled and my opinion does not add value; in that case, you already have what you want.
But, lets assume you want to model a relationship between users
and pets
that is non-hierarchical. I can think of three basic usages here that would result in two different looking types of URIs.
Let's say that you don't want to consider "pets" and "people" to be distinct entities. Let's say you are using the name of a pet to find a person. You could model that like so
/customers?pet=Zeus
Perhaps say you are modeling a pet shop and you consider customers to be a core domain object. By using this structure, you will unsurprisingly retrieve customers having a pet named Zeus.
Let's say that you want to use the name of a person to find pets
/pets?owner=Alan
Perhaps you are modeling a veterinary clinic and pets are the core domain object of your medication tracking system. Using this structure will unsurprisingly retrieve pets owned by Alan.
For the final set of examples, let's assume that you want to model a close relationship between "owners" and "pets" without modeling the relationship hierarchically. You want to use a context defined in the path to retrieve some piece of data that is related to but not semantically coupled to the structure of the path. You can use the fragment for that (before using this, seek a definition of predefined semantics for your representation's media type to see if this usage is valid. In absence of predefined semantics, the fragment's usage is unconstrained and left to the design of the server. In that event, this would be valid.)
Retrieve the pet named Zeus for user Alan
/users;name=Alan#pet=Zeus
Retrieve the owner of pet named Zeus
/pets/Zeus#owner
/veterinary/patients;owner=Alan#pet=Zeus
For the final one, you are searching veterinary records by using the entity with name=Alan
to retrieve a pet named Zeus, or more succinctly you are taking Alan's dog.
Ooooohhhhh noooooooeeeee.